@sema-agent/core 5.65.0 → 7.0.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 +58 -0
- package/dist/agents/subagent.d.ts +2 -2
- package/dist/agents/subagent.js +11 -0
- package/dist/agents/verify.d.ts +1 -1
- package/dist/brain/anthropic.js +1 -1
- package/dist/brain/errors.d.ts +29 -0
- package/dist/brain/errors.js +20 -0
- package/dist/brain/open-responses.js +2 -2
- package/dist/brain/route-adjudicator.d.ts +8 -1
- package/dist/brain/route-adjudicator.js +1 -0
- package/dist/brain/status-sink.js +12 -1
- package/dist/brain/stream-engine.js +17 -6
- package/dist/core/auto-compaction.d.ts +26 -0
- package/dist/core/auto-compaction.js +7 -2
- package/dist/core/auto-mode-arming.d.ts +138 -0
- package/dist/core/auto-mode-arming.js +181 -0
- package/dist/core/auto-mode-defaults.d.ts +13 -0
- package/dist/core/auto-mode-defaults.js +5 -0
- package/dist/core/auto-mode-prompt.d.ts +14 -3
- package/dist/core/auto-mode-prompt.js +10 -7
- package/dist/core/auto-mode-rebuild.d.ts +75 -0
- package/dist/core/auto-mode-rebuild.js +41 -0
- package/dist/core/auto-mode.d.ts +15 -0
- package/dist/core/auto-mode.js +4 -2
- package/dist/core/checkpoint-store.d.ts +18 -0
- package/dist/core/context-edit.d.ts +47 -5
- package/dist/core/context-guard.d.ts +1 -1
- package/dist/core/file-history-retention.d.ts +106 -0
- package/dist/core/file-history-retention.js +36 -0
- package/dist/core/file-history-store.d.ts +768 -0
- package/dist/core/file-history-store.js +880 -0
- package/dist/core/governance-codes.d.ts +13 -3
- package/dist/core/governance-codes.js +15 -0
- package/dist/core/hooks.d.ts +39 -7
- package/dist/core/hooks.js +38 -21
- package/dist/core/lsp.d.ts +2 -2
- package/dist/core/mcp.d.ts +29 -7
- package/dist/core/memory-engine/consolidation-driver.d.ts +11 -0
- package/dist/core/memory-engine/consolidation-driver.js +71 -4
- package/dist/core/memory-engine/consolidation.d.ts +25 -2
- package/dist/core/memory-engine/consolidation.js +4 -1
- package/dist/core/memory-engine/distiller.d.ts +84 -1
- package/dist/core/memory-engine/distiller.js +68 -0
- package/dist/core/memory-engine/dual-root.js +3 -0
- package/dist/core/memory-engine/engine.d.ts +328 -15
- package/dist/core/memory-engine/engine.js +355 -29
- package/dist/core/memory-engine/file-backend.d.ts +30 -0
- package/dist/core/memory-engine/file-backend.js +14 -13
- package/dist/core/memory-engine/frontmatter.d.ts +22 -1
- package/dist/core/memory-engine/frontmatter.js +3 -0
- package/dist/core/memory-engine/header-hints.d.ts +5 -0
- package/dist/core/memory-engine/index.d.ts +5 -4
- package/dist/core/memory-engine/index.js +5 -4
- package/dist/core/memory-engine/layout.d.ts +88 -2
- package/dist/core/memory-engine/layout.js +112 -3
- package/dist/core/memory-engine/provenance-wording.d.ts +7 -0
- package/dist/core/memory-engine/provenance-wording.js +3 -0
- package/dist/core/memory-engine/tools.d.ts +89 -8
- package/dist/core/memory-engine/tools.js +263 -22
- package/dist/core/memory-engine/types.d.ts +64 -1
- package/dist/core/memory-recall.d.ts +6 -0
- package/dist/core/memory.d.ts +27 -1
- package/dist/core/memory.js +16 -2
- package/dist/core/permission-rule-consent.d.ts +62 -2
- package/dist/core/permission-rule-consent.js +105 -14
- package/dist/core/permission-rule-model.d.ts +118 -16
- package/dist/core/permission-rule-model.js +57 -9
- package/dist/core/permission-rule-session.d.ts +124 -0
- package/dist/core/permission-rule-session.js +121 -0
- package/dist/core/permission-rule-store.d.ts +65 -2
- package/dist/core/permission-rule-store.js +75 -16
- package/dist/core/permission-rule-sync.d.ts +9 -0
- package/dist/core/permission-rule-sync.js +52 -19
- package/dist/core/retention-policy.d.ts +9 -0
- package/dist/core/retention-policy.js +5 -2
- package/dist/core/retention.d.ts +13 -2
- package/dist/core/runner/assemble-result.d.ts +19 -1
- package/dist/core/runner/assemble-result.js +17 -2
- package/dist/core/runner/compaction-call-options.d.ts +93 -0
- package/dist/core/runner/compaction-call-options.js +3 -0
- package/dist/core/runner/memory-capture-optout.d.ts +80 -0
- package/dist/core/runner/memory-capture-optout.js +53 -0
- package/dist/core/runner/prepare-config-doors.d.ts +5 -0
- package/dist/core/runner/prepare-config-doors.js +16 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +110 -5
- package/dist/core/runner/prepare-hands-readface.js +99 -7
- package/dist/core/runner/prepare-memory.d.ts +88 -0
- package/dist/core/runner/prepare-memory.js +305 -24
- package/dist/core/runner/prepare-task.d.ts +141 -1
- package/dist/core/runner/prepare-task.js +478 -81
- package/dist/core/runner/runtask.d.ts +9 -20
- package/dist/core/runner/runtask.js +133 -96
- package/dist/core/runner/session-file-state-replay.d.ts +18 -10
- package/dist/core/runner/session-file-state-replay.js +52 -1
- package/dist/core/runner/tool-disclosure.js +2 -1
- package/dist/core/runner/turn-attachments.d.ts +22 -12
- package/dist/core/session-store.d.ts +1 -1
- package/dist/core/session-store.js +6 -1
- package/dist/core/session.d.ts +34 -1
- package/dist/core/store-contracts/file-history-store-contract.d.ts +3 -0
- package/dist/core/store-contracts/file-history-store-contract.js +720 -0
- package/dist/core/store-contracts/permission-rule-sync-contract.js +15 -1
- package/dist/core/task-registry-shared.js +11 -1
- package/dist/core/tool-errors.js +1 -0
- package/dist/core/tool-policy.d.ts +172 -1
- package/dist/core/tool-policy.js +32 -1
- package/dist/core/tool-result-store.js +2 -1
- package/dist/core/trace.d.ts +31 -2
- package/dist/core/types.d.ts +795 -89
- package/dist/core/types.js +4 -3
- package/dist/core/untrusted-text.d.ts +1 -1
- package/dist/core/untrusted-text.js +8 -0
- package/dist/core/workflow-run-store-contract.js +6 -2
- package/dist/core/workflow-run-store.d.ts +4 -1
- package/dist/engine/compaction/compaction.d.ts +88 -10
- package/dist/engine/compaction/compaction.js +109 -30
- package/dist/engine/execution-env/node-execution-env.d.ts +9 -1
- package/dist/engine/execution-env/node-execution-env.js +28 -0
- package/dist/engine/harness/agent-harness.d.ts +52 -1
- package/dist/engine/harness/agent-harness.js +36 -1
- package/dist/engine/harness/types.d.ts +26 -1
- package/dist/engine/llm/types.d.ts +50 -4
- package/dist/engine/loop/agent-loop.d.ts +5 -1
- package/dist/engine/loop/agent-loop.js +25 -0
- package/dist/engine/loop/types.d.ts +19 -0
- package/dist/engine/lsp/node-lsp-manager.d.ts +1 -1
- package/dist/engine/session/session.js +1 -1
- package/dist/index.d.ts +20 -9
- package/dist/index.js +16 -7
- package/dist/orchestration/run-workflow-tool.d.ts +20 -2
- package/dist/orchestration/run-workflow-tool.js +22 -3
- package/dist/orchestration/workflow-governance.d.ts +59 -1
- package/dist/orchestration/workflow-governance.js +61 -8
- package/dist/orchestration/workflow-meta.d.ts +4 -2
- package/dist/orchestration/workflow-primitives.js +56 -13
- package/dist/orchestration/workflow-types.d.ts +78 -2
- package/dist/orchestration/workflow.d.ts +20 -0
- package/dist/orchestration/workflow.js +163 -14
- package/dist/prompt-assembly/event-registry.js +1 -1
- package/dist/prompts/default.d.ts +7 -7
- package/dist/stores/file/file-history-store.d.ts +368 -0
- package/dist/stores/file/file-history-store.js +1248 -0
- package/dist/stores/file/index.d.ts +22 -13
- package/dist/stores/file/index.js +4 -4
- package/dist/stores/file/permission-rule-store.d.ts +11 -0
- package/dist/stores/file/permission-rule-store.js +23 -9
- package/dist/stores/file/strategy-store.d.ts +3 -3
- package/dist/tools/fs/bash-readonly-classifier.d.ts +87 -3
- package/dist/tools/fs/bash-readonly-classifier.js +106 -4
- package/dist/tools/fs/fs-bash.js +9 -5
- package/dist/tools/fs/fs-shared.d.ts +52 -1
- package/dist/tools/fs/fs-shared.js +14 -0
- package/dist/tools/fs/fs-write.d.ts +5 -5
- package/dist/tools/fs/fs-write.js +71 -14
- package/dist/tools/fs/index.d.ts +6 -1
- package/dist/tools/fs/index.js +1 -1
- package/dist/tools/web.js +2 -1
- package/package.json +5 -1
- package/test/export-surface.snapshot.json +169 -23
- package/dist/core/file-snapshot-store.d.ts +0 -165
- package/dist/core/file-snapshot-store.js +0 -259
- package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +0 -13
- package/dist/core/store-contracts/file-snapshot-store-contract.js +0 -134
- package/dist/stores/file/file-snapshot-store.d.ts +0 -58
- package/dist/stores/file/file-snapshot-store.js +0 -353
|
@@ -74,6 +74,35 @@ export declare const BASH_READONLY_CONFINEMENT_NOTE = "bash_readonly is confined
|
|
|
74
74
|
* false-stale defect for those targets. Round-tripping the real bytes sidesteps the case analysis
|
|
75
75
|
* and stays correct if the encoding rules ever change. */
|
|
76
76
|
export declare function persistedTextOf(encoded: string | Uint8Array): string;
|
|
77
|
+
/**
|
|
78
|
+
* #483 — one APPLIED edit as the Edit tool's `details` card records it: the RESOLVED old/new strings
|
|
79
|
+
* (post quote/escape resolution, post deletion-newline widening) in the normalized originalFile
|
|
80
|
+
* coordinate. This is the card shape a transcript replay must re-apply, so the application step is a
|
|
81
|
+
* single source shared by the Edit tool's own apply loop and the replay — two spellings of "apply"
|
|
82
|
+
* could silently drift (e.g. one expanding `$&` and one not) and the replay's recomputed hash would
|
|
83
|
+
* refuse edits the tool actually made.
|
|
84
|
+
*/
|
|
85
|
+
export interface RecordedEdit {
|
|
86
|
+
oldString: string;
|
|
87
|
+
newString: string;
|
|
88
|
+
replaceAll: boolean;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* #483 — apply ONE recorded edit mechanically (no matching layers, no validation: the recording site
|
|
92
|
+
* already resolved and validated the strings; a replay consumer that cannot prove `oldString` is
|
|
93
|
+
* present must fall back to retracting its record, never guess here).
|
|
94
|
+
*
|
|
95
|
+
* Two load-bearing details, both shared with the Edit tool's historical inline apply:
|
|
96
|
+
* - Function-form replacement: the callback's return value is NOT interpreted as a replacement
|
|
97
|
+
* pattern, so `$&`/`$$`/`` $` ``/`$'` in `newString` are written literally (S-1; the replaceAll
|
|
98
|
+
* split/join path was always literal).
|
|
99
|
+
* - `oldString === ""` is the whole-content OVERWRITE shape: the Edit tool's whitespace-only-file
|
|
100
|
+
* overwrite lane records `{oldString: "", newString: <full new text>}` after REPLACING the file's
|
|
101
|
+
* content wholesale (fs-write.ts, the `old_string:""` existing-file branch). The tool's own apply
|
|
102
|
+
* loop never reaches this arm (checkEditMatch refuses an empty old_string before application), so
|
|
103
|
+
* it exists for the replay consumer alone — a plain `replace("", ...)` would PREPEND instead.
|
|
104
|
+
*/
|
|
105
|
+
export declare function applyRecordedEdit(text: string, edit: RecordedEdit): string;
|
|
77
106
|
/** fidelity-sweep F3 (stopgap wording): render the read-before-edit refusal, appending the slice/bash
|
|
78
107
|
* escape hatch when the target is over the Read byte cap — a bare "read it first" is un-followable
|
|
79
108
|
* there (a default whole-file Read refuses such files). Best-effort: an un-stattable file keeps the
|
|
@@ -114,7 +143,7 @@ export declare function resolveAutoDownsampler(): Promise<ImageDownsampler | und
|
|
|
114
143
|
* proxy — `KMu` @279761 pre-filters with a PER-EXTENSION chars-per-token coefficient (json/jsonl = 2,
|
|
115
144
|
* i.e. half of the 4 assumed here) and then confirms with a REAL tokenizer (`BLd` @518589); the
|
|
116
145
|
* budget itself is also overridable per call (`fileReadingLimits`) and by the `tengu_amber_wren` gate
|
|
117
|
-
* (`eze` @335063), neither of which has a sema lane. Consequence, stated rather than hidden: on
|
|
146
|
+
* (`eze` @335063 (cli250.js:39563)), neither of which has a sema lane. Consequence, stated rather than hidden: on
|
|
118
147
|
* token-dense text (JSON/CJK/base64) this proxy UNDER-counts, so sema serves a page CC would have cut
|
|
119
148
|
* further; on token-sparse text it over-counts and paginates earlier than CC. A real tokenizer here
|
|
120
149
|
* would need a provider-neutral counting seam (BYOM — no single vocabulary), which is why the proxy
|
|
@@ -416,6 +445,28 @@ export declare function seedReadFileStateFromContext(state: ReadFileState, key:
|
|
|
416
445
|
* compaction. This text lives in the transcript and does not.
|
|
417
446
|
*/
|
|
418
447
|
export declare function seedReadFileStateFromTranscript(state: ReadFileState, key: string, content: string, lastReadAt: number): void;
|
|
448
|
+
/**
|
|
449
|
+
* #483 rung-1 — copy a SESSION-inherited read-file state into a new task's map, under the new task's
|
|
450
|
+
* own containment judgment (CC parity: readFileState is session-scoped — minted once per REPL session,
|
|
451
|
+
* mutated across turns, with no turn-boundary or abort clear site; sema's per-task map inherits the
|
|
452
|
+
* previous turn's live state instead).
|
|
453
|
+
*
|
|
454
|
+
* Two rules, each a claim inheritance cannot honestly skip:
|
|
455
|
+
* - **Every key passes the CURRENT task's own key judgment** (`judgeKey` = the same resolveKey
|
|
456
|
+
* containment the transcript-replay seed uses: write roots ∪ read roots, resolved read face). A
|
|
457
|
+
* root or face narrowed between the turns must narrow what the state remembers, exactly as the
|
|
458
|
+
* replay leg already judges — an unjudged copy would smuggle out-of-face entries into the task
|
|
459
|
+
* (and into its checkpoints).
|
|
460
|
+
* - **`seededFromContext` entries do not inherit.** That flag claims the file's text lives in the
|
|
461
|
+
* system-prompt lane OF THIS TASK — a claim only this task's own startup seeding (T10) can make.
|
|
462
|
+
* A still-declared file is re-seeded the same turn (no loss); a no-longer-declared one must not
|
|
463
|
+
* keep answering reads with an "already in your context" reminder that is no longer true.
|
|
464
|
+
*
|
|
465
|
+
* Entries are copied by REFERENCE (ReadEntry values are never mutated in place — every write-back
|
|
466
|
+
* `set`s a fresh object), so recorded facts (view / isPartialView / lastReadAt) ride verbatim: the
|
|
467
|
+
* dedup stub and the partial-view refusal keep working across turns exactly as inside one task.
|
|
468
|
+
*/
|
|
469
|
+
export declare function inheritSessionReadFileState(target: ReadFileState, inherited: ReadFileState, judgeKey: (recordedKey: string) => Promise<string | undefined>): Promise<void>;
|
|
419
470
|
/**
|
|
420
471
|
* RB-197 (form-one audit, CC 2.1.220 @388663-388664): a landed compaction summarizes old Read results OUT
|
|
421
472
|
* of the context, so every non-seeded read-state entry must be dropped — CC snapshots then
|
|
@@ -39,6 +39,11 @@ export const BASH_READONLY_CONFINEMENT_NOTE = "bash_readonly is confined to the
|
|
|
39
39
|
export function persistedTextOf(encoded) {
|
|
40
40
|
return decodeTextBytes(typeof encoded === "string" ? Buffer.from(encoded, "utf8") : encoded).text;
|
|
41
41
|
}
|
|
42
|
+
export function applyRecordedEdit(text, edit) {
|
|
43
|
+
if (edit.oldString === "")
|
|
44
|
+
return edit.newString;
|
|
45
|
+
return edit.replaceAll ? text.split(edit.oldString).join(edit.newString) : text.replace(edit.oldString, () => edit.newString);
|
|
46
|
+
}
|
|
42
47
|
export async function notReadRefusalText(env, toolName, key, v, signal, fallbackHint) {
|
|
43
48
|
const base = `Error (${toolName}): ${v.message}`;
|
|
44
49
|
if (v.partialView === true && !isNotebookPath(key))
|
|
@@ -276,6 +281,15 @@ export function seedReadFileStateFromTranscript(state, key, content, lastReadAt)
|
|
|
276
281
|
const text = normalizeFileText(content);
|
|
277
282
|
state.set(key, { hash: sha256(text), totalLines: countLines(text), truncated: false, lastReadAt });
|
|
278
283
|
}
|
|
284
|
+
export async function inheritSessionReadFileState(target, inherited, judgeKey) {
|
|
285
|
+
for (const [key, entry] of inherited) {
|
|
286
|
+
if (entry.seededFromContext === true)
|
|
287
|
+
continue;
|
|
288
|
+
const judged = await judgeKey(key);
|
|
289
|
+
if (judged !== undefined)
|
|
290
|
+
target.set(judged, entry);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
279
293
|
export function applyCompactionToReadFileState(state, attachedComplete, preserveKeys = []) {
|
|
280
294
|
const preserve = new Set(preserveKeys);
|
|
281
295
|
for (const [k, v] of [...state]) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentTool, ExecutionEnv } from "../../internal/harness-types.js";
|
|
2
|
-
import type { BeforeWriteHook } from "../../core/types.js";
|
|
2
|
+
import type { BeforeWriteHook, TrackFileEditHook } from "../../core/types.js";
|
|
3
3
|
import { type ReadFileState } from "./safety.js";
|
|
4
4
|
import { type CwdRef } from "./fs-shared.js";
|
|
5
5
|
/**
|
|
@@ -11,9 +11,9 @@ import { type CwdRef } from "./fs-shared.js";
|
|
|
11
11
|
* MemoryEngine's write gate (`MemoryEngine.gateWrite`) — a non-memory path passes with one string
|
|
12
12
|
* prefix comparison there (零开销直通). Absent hook ⇒ byte-identical behavior.
|
|
13
13
|
*/
|
|
14
|
-
export type { BeforeWriteRequest, BeforeWriteResult, BeforeWriteHook } from "../../core/types.js";
|
|
15
|
-
export declare function createEditFileTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: readonly string[], beforeWrite?: BeforeWriteHook): AgentTool;
|
|
16
|
-
export declare function createWriteFileTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: readonly string[], beforeWrite?: BeforeWriteHook): AgentTool;
|
|
14
|
+
export type { BeforeWriteRequest, BeforeWriteResult, BeforeWriteHook, TrackEditRequest, TrackEditResult, TrackFileEditHook } from "../../core/types.js";
|
|
15
|
+
export declare function createEditFileTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: readonly string[], beforeWrite?: BeforeWriteHook, trackEdit?: TrackFileEditHook): AgentTool;
|
|
16
|
+
export declare function createWriteFileTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: readonly string[], beforeWrite?: BeforeWriteHook, trackEdit?: TrackFileEditHook): AgentTool;
|
|
17
17
|
/**
|
|
18
18
|
* design v1.163 — NotebookEdit: replace/insert/delete a single cell in a .ipynb. CC-parity tool over the SAME hand-band
|
|
19
19
|
* safety skeleton as Edit/Write (resolveKey containment → requireRead read-before-edit → checkStale content-hash
|
|
@@ -21,4 +21,4 @@ export declare function createWriteFileTool(env: ExecutionEnv, state: ReadFileSt
|
|
|
21
21
|
* .ipynb as the RB-227 cell projection but records read state (hash/totalLines) in the RAW notebook-text
|
|
22
22
|
* coordinate — that raw-coordinate read record is what this tool's freshness check depends on.
|
|
23
23
|
*/
|
|
24
|
-
export declare function createNotebookEditTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: readonly string[], beforeWrite?: BeforeWriteHook): AgentTool;
|
|
24
|
+
export declare function createNotebookEditTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: readonly string[], beforeWrite?: BeforeWriteHook, trackEdit?: TrackFileEditHook): AgentTool;
|
|
@@ -3,8 +3,22 @@ import { Type } from "typebox";
|
|
|
3
3
|
import { defineTool, errorResult } from "../../core/tools.js";
|
|
4
4
|
import { sha256, resolveKey, violationText, violationDetails, requireRead, checkStale, checkEditMatch, checkNoChange, fileArgPath, resolveQuoteMatch, adaptNewStringQuotes, resolveEscapeMatch, adaptNewStringEscapes, escapeMatchWasAttempted, ESCAPE_MATCH_MISS_NOTE, deletionOldString, countOccurrences, READ_REFUSED_ESCAPE_HINT, } from "./safety.js";
|
|
5
5
|
import { decodeTextBytes, encodeTextForFile, normalizeEditText, normalizeFileText } from "./encoding.js";
|
|
6
|
-
import { MAX_EDIT_BYTES, decodeEditBytes, tooLargeToEditMessage, truncatedUtf16BodyMessage, persistedTextOf, notReadRefusalText, enoentMessage, FILE_STATE_TRAILER, FILE_PATH_PARAMS, ipynbRedirect, countLines, } from "./fs-shared.js";
|
|
6
|
+
import { MAX_EDIT_BYTES, decodeEditBytes, tooLargeToEditMessage, truncatedUtf16BodyMessage, persistedTextOf, notReadRefusalText, enoentMessage, FILE_STATE_TRAILER, FILE_PATH_PARAMS, ipynbRedirect, countLines, applyRecordedEdit, } from "./fs-shared.js";
|
|
7
7
|
async function envFinalWrite(env, key, content, signal, opts) {
|
|
8
|
+
let r;
|
|
9
|
+
try {
|
|
10
|
+
r = await writeThroughEnv(env, key, content, signal, opts);
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
await discardTrackedEdit(opts?.track, "verify");
|
|
14
|
+
throw err;
|
|
15
|
+
}
|
|
16
|
+
if (!r.ok)
|
|
17
|
+
await discardTrackedEdit(opts?.track, NOTHING_WAS_WRITTEN.has(r.error.code) ? "proven" : "verify");
|
|
18
|
+
return r;
|
|
19
|
+
}
|
|
20
|
+
const NOTHING_WAS_WRITTEN = new Set(["already_exists", "precondition_failed"]);
|
|
21
|
+
async function writeThroughEnv(env, key, content, signal, opts) {
|
|
8
22
|
if (env.writeFileGuarded !== undefined) {
|
|
9
23
|
const r = await env.writeFileGuarded(key, content, { canonicalPath: key, ...(opts?.exclusive === true ? { exclusive: true } : {}) }, signal);
|
|
10
24
|
return r.ok ? { ok: true } : r;
|
|
@@ -14,6 +28,30 @@ async function envFinalWrite(env, key, content, signal, opts) {
|
|
|
14
28
|
}
|
|
15
29
|
return env.writeFile(key, content, signal);
|
|
16
30
|
}
|
|
31
|
+
async function discardTrackedEdit(lease, proof) {
|
|
32
|
+
if (lease?.annul === undefined)
|
|
33
|
+
return;
|
|
34
|
+
try {
|
|
35
|
+
await lease.annul(proof);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const clipHookText = (s) => (s.length > 300 ? `${s.slice(0, 300)}…` : s);
|
|
41
|
+
async function trackToolEdit(hook, tool, path, key, signal) {
|
|
42
|
+
if (hook === undefined)
|
|
43
|
+
return {};
|
|
44
|
+
let res;
|
|
45
|
+
try {
|
|
46
|
+
res = await hook({ tool, path, key, ...(signal !== undefined ? { signal } : {}) });
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
return { refusal: `Error (${tool}): the file-history first-touch backup failed (${clipHookText(err instanceof Error ? err.message : String(err))}) — nothing was written.` };
|
|
50
|
+
}
|
|
51
|
+
if (res.ok === false)
|
|
52
|
+
return { refusal: clipHookText(res.refusal) };
|
|
53
|
+
return res.annul !== undefined ? { annul: res.annul } : {};
|
|
54
|
+
}
|
|
17
55
|
async function gateToolWrite(hook, tool, path, key, content) {
|
|
18
56
|
if (hook === undefined)
|
|
19
57
|
return undefined;
|
|
@@ -22,13 +60,13 @@ async function gateToolWrite(hook, tool, path, key, content) {
|
|
|
22
60
|
res = await hook({ tool, path, key, content });
|
|
23
61
|
}
|
|
24
62
|
catch (err) {
|
|
25
|
-
return `Error (${tool}): the write gate failed (${err instanceof Error ? err.message : String(err)}) — nothing was written.`;
|
|
63
|
+
return `Error (${tool}): the write gate failed (${clipHookText(err instanceof Error ? err.message : String(err))}) — nothing was written.`;
|
|
26
64
|
}
|
|
27
65
|
if (res !== undefined && res.ok === false)
|
|
28
|
-
return `Error (${tool}): write rejected by the write gate (${res.code}): ${res.reason}`;
|
|
66
|
+
return `Error (${tool}): write rejected by the write gate (${res.code}): ${clipHookText(res.reason)}`;
|
|
29
67
|
return undefined;
|
|
30
68
|
}
|
|
31
|
-
export function createEditFileTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite) {
|
|
69
|
+
export function createEditFileTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit) {
|
|
32
70
|
return defineTool({
|
|
33
71
|
name: "Edit",
|
|
34
72
|
contract: { contractId: "core.edit@1", implementationRevision: "1" },
|
|
@@ -81,7 +119,10 @@ export function createEditFileTool(env, state, rootCanonical, cwdRef, additional
|
|
|
81
119
|
const gated = await gateToolWrite(beforeWrite, "Edit", path, r.key, created);
|
|
82
120
|
if (gated !== undefined)
|
|
83
121
|
return errorResult(gated);
|
|
84
|
-
const
|
|
122
|
+
const tracked = await trackToolEdit(trackEdit, "Edit", path, r.key, ctx.signal);
|
|
123
|
+
if (tracked.refusal !== undefined)
|
|
124
|
+
return errorResult(tracked.refusal);
|
|
125
|
+
const write = await envFinalWrite(env, r.key, created, ctx.signal, { exclusive: true, track: tracked });
|
|
85
126
|
if (!write.ok) {
|
|
86
127
|
if (write.error.code === "already_exists") {
|
|
87
128
|
return errorResult(`Error (Edit): cannot create "${path}": file already exists (created concurrently since the existence check). Read the file first, then edit it normally (or Write after the Read to overwrite it).`);
|
|
@@ -131,8 +172,11 @@ export function createEditFileTool(env, state, rootCanonical, cwdRef, additional
|
|
|
131
172
|
const gated = await gateToolWrite(beforeWrite, "Edit", path, r.key, newContent);
|
|
132
173
|
if (gated !== undefined)
|
|
133
174
|
return errorResult(gated);
|
|
175
|
+
const trackedOverwrite = await trackToolEdit(trackEdit, "Edit", path, r.key, ctx.signal);
|
|
176
|
+
if (trackedOverwrite.refusal !== undefined)
|
|
177
|
+
return errorResult(trackedOverwrite.refusal);
|
|
134
178
|
const encodedOverwrite = encodeTextForFile(newContent, preDec.encoding, preDec.endings);
|
|
135
|
-
const write = await envFinalWrite(env, r.key, encodedOverwrite, ctx.signal);
|
|
179
|
+
const write = await envFinalWrite(env, r.key, encodedOverwrite, ctx.signal, { track: trackedOverwrite });
|
|
136
180
|
if (!write.ok)
|
|
137
181
|
return errorResult(`Error (Edit): cannot write "${path}": ${write.error.message}`);
|
|
138
182
|
const persistedOverwrite = persistedTextOf(encodedOverwrite);
|
|
@@ -198,14 +242,18 @@ export function createEditFileTool(env, state, rootCanonical, cwdRef, additional
|
|
|
198
242
|
}
|
|
199
243
|
replacements += e.replace_all === true ? countOccurrences(working, oldS) : 1;
|
|
200
244
|
const effOld = e.replace_all === true ? oldS : deletionOldString(working, oldS, newS);
|
|
201
|
-
|
|
202
|
-
|
|
245
|
+
const applied = { oldString: effOld, newString: newS, replaceAll: e.replace_all === true };
|
|
246
|
+
working = applyRecordedEdit(working, applied);
|
|
247
|
+
appliedEdits.push(applied);
|
|
203
248
|
}
|
|
204
249
|
const gated = await gateToolWrite(beforeWrite, "Edit", path, r.key, working);
|
|
205
250
|
if (gated !== undefined)
|
|
206
251
|
return errorResult(gated);
|
|
252
|
+
const trackedEdit = await trackToolEdit(trackEdit, "Edit", path, r.key, ctx.signal);
|
|
253
|
+
if (trackedEdit.refusal !== undefined)
|
|
254
|
+
return errorResult(trackedEdit.refusal);
|
|
207
255
|
const encodedEdit = encodeTextForFile(working, decoded.encoding, decoded.endings);
|
|
208
|
-
const write = await envFinalWrite(env, r.key, encodedEdit, ctx.signal);
|
|
256
|
+
const write = await envFinalWrite(env, r.key, encodedEdit, ctx.signal, { track: trackedEdit });
|
|
209
257
|
if (!write.ok)
|
|
210
258
|
return errorResult(`Error (Edit): cannot write "${path}": ${write.error.message}`);
|
|
211
259
|
const persistedEdit = persistedTextOf(encodedEdit);
|
|
@@ -228,7 +276,7 @@ export function createEditFileTool(env, state, rootCanonical, cwdRef, additional
|
|
|
228
276
|
},
|
|
229
277
|
});
|
|
230
278
|
}
|
|
231
|
-
export function createWriteFileTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite) {
|
|
279
|
+
export function createWriteFileTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit) {
|
|
232
280
|
return defineTool({
|
|
233
281
|
name: "Write",
|
|
234
282
|
contract: { contractId: "core.write@1", implementationRevision: "1" },
|
|
@@ -275,8 +323,11 @@ export function createWriteFileTool(env, state, rootCanonical, cwdRef, additiona
|
|
|
275
323
|
const gated = await gateToolWrite(beforeWrite, "Write", path, r.key, content);
|
|
276
324
|
if (gated !== undefined)
|
|
277
325
|
return errorResult(gated);
|
|
326
|
+
const trackedWrite = await trackToolEdit(trackEdit, "Write", path, r.key, ctx.signal);
|
|
327
|
+
if (trackedWrite.refusal !== undefined)
|
|
328
|
+
return errorResult(trackedWrite.refusal);
|
|
278
329
|
const encodedWrite = encodeTextForFile(content, decodedPrev.encoding, "preserve");
|
|
279
|
-
const write = await envFinalWrite(env, r.key, encodedWrite, ctx.signal);
|
|
330
|
+
const write = await envFinalWrite(env, r.key, encodedWrite, ctx.signal, { track: trackedWrite });
|
|
280
331
|
if (!write.ok)
|
|
281
332
|
return errorResult(`Error (Write): cannot write "${path}": ${write.error.message}`);
|
|
282
333
|
const persistedWrite = persistedTextOf(encodedWrite);
|
|
@@ -289,7 +340,10 @@ export function createWriteFileTool(env, state, rootCanonical, cwdRef, additiona
|
|
|
289
340
|
const gatedCreate = await gateToolWrite(beforeWrite, "Write", path, r.key, content);
|
|
290
341
|
if (gatedCreate !== undefined)
|
|
291
342
|
return errorResult(gatedCreate);
|
|
292
|
-
const
|
|
343
|
+
const trackedCreate = await trackToolEdit(trackEdit, "Write", path, r.key, ctx.signal);
|
|
344
|
+
if (trackedCreate.refusal !== undefined)
|
|
345
|
+
return errorResult(trackedCreate.refusal);
|
|
346
|
+
const write = await envFinalWrite(env, r.key, content, ctx.signal, { track: trackedCreate });
|
|
293
347
|
if (!write.ok)
|
|
294
348
|
return errorResult(`Error (Write): cannot write "${path}": ${write.error.message}`);
|
|
295
349
|
const totalLines = countLines(content);
|
|
@@ -302,7 +356,7 @@ export function createWriteFileTool(env, state, rootCanonical, cwdRef, additiona
|
|
|
302
356
|
},
|
|
303
357
|
});
|
|
304
358
|
}
|
|
305
|
-
export function createNotebookEditTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite) {
|
|
359
|
+
export function createNotebookEditTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit) {
|
|
306
360
|
return defineTool({
|
|
307
361
|
name: "NotebookEdit",
|
|
308
362
|
contract: { contractId: "core.notebook_edit@1", implementationRevision: "1" },
|
|
@@ -420,7 +474,10 @@ export function createNotebookEditTool(env, state, rootCanonical, cwdRef, additi
|
|
|
420
474
|
const gated = await gateToolWrite(beforeWrite, "NotebookEdit", notebook_path, r.key, updated);
|
|
421
475
|
if (gated !== undefined)
|
|
422
476
|
return errorResult(gated);
|
|
423
|
-
const
|
|
477
|
+
const trackedNb = await trackToolEdit(trackEdit, "NotebookEdit", notebook_path, r.key, ctx.signal);
|
|
478
|
+
if (trackedNb.refusal !== undefined)
|
|
479
|
+
return errorResult(trackedNb.refusal);
|
|
480
|
+
const w = await envFinalWrite(env, r.key, encodeTextForFile(updated, decodedNb.encoding, decodedNb.endings), ctx.signal, { track: trackedNb });
|
|
424
481
|
if (!w.ok)
|
|
425
482
|
return errorResult(`Error (NotebookEdit): cannot write "${notebook_path}": ${w.error.message}`);
|
|
426
483
|
state.set(r.key, { hash: sha256(updated), totalLines: countLines(updated), truncated: false, lastReadAt: Date.now() });
|
package/dist/tools/fs/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentTool, ExecutionEnv } from "../../internal/harness-types.js";
|
|
2
|
-
import { type BeforeWriteHook } from "../../core/types.js";
|
|
2
|
+
import { type BeforeWriteHook, type TrackFileEditHook } from "../../core/types.js";
|
|
3
3
|
import { type TaskRegistry } from "../../core/task-registry.js";
|
|
4
4
|
import { type ReadFileState } from "./safety.js";
|
|
5
5
|
import type { PdfModelCapabilities } from "./pdf.js";
|
|
@@ -115,6 +115,11 @@ export interface HandsToolkitOptions {
|
|
|
115
115
|
/** design/138 S2-C — write-time content gate for Write/Edit/NotebookEdit (see {@link BeforeWriteHook}).
|
|
116
116
|
* The Runner wires the MemoryEngine's memory-domain scan here; absent ⇒ byte-identical behavior. */
|
|
117
117
|
beforeWrite?: BeforeWriteHook;
|
|
118
|
+
/** design/381 — first-touch file-history hook for Write/Edit/NotebookEdit (see
|
|
119
|
+
* {@link TrackFileEditHook}): called after the content gates pass, immediately before the final
|
|
120
|
+
* env write, so the file's pre-edit state is durably recorded before it changes. The Runner
|
|
121
|
+
* wires it to `FileHistoryStore.trackEdit`; absent ⇒ byte-identical behavior (no history). */
|
|
122
|
+
trackFileEdit?: TrackFileEditHook;
|
|
118
123
|
/** #181-F6 — see createBashTool's taskOpts field of the same name: whether the Monitor tool is on
|
|
119
124
|
* this run's roster (the Runner mounts Monitor, this band never does). `false` drops the gh
|
|
120
125
|
* rate-limit hint's Monitor clause; absent ⇒ historic full wording (byte-compat). */
|
package/dist/tools/fs/index.js
CHANGED
|
@@ -50,7 +50,7 @@ export function createHandsToolkit(env, readFileState, rootCanonical, opts = {})
|
|
|
50
50
|
createReadFileTool(env, readFileState, rootCanonical, readOnly ? undefined : cwdRef, readFaceRoots, opts.readImageDownsampler, opts.pdfModelCapabilities, bgOutputReadExemption, opts.readCyberReminder, readDeny, readFace, opts.reminderMark, opts.reminderDisclosureCounts),
|
|
51
51
|
];
|
|
52
52
|
if (!readOnly) {
|
|
53
|
-
tools.push(createEditFileTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite), createWriteFileTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite), createNotebookEditTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite));
|
|
53
|
+
tools.push(createEditFileTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit), createWriteFileTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit), createNotebookEditTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit));
|
|
54
54
|
}
|
|
55
55
|
tools.push(createGrepTool(env, rootCanonical, readFaceRoots, readDeny, readFace), createGlobTool(env, rootCanonical, readFaceRoots, readDeny, readFace), createRepoMapTool(env, rootCanonical, readFaceRoots, readDeny, readFace));
|
|
56
56
|
if (includeShell) {
|
package/dist/tools/web.js
CHANGED
|
@@ -4,6 +4,7 @@ import { delimitUntrusted, inlineUntrusted } from "../core/untrusted-text.js";
|
|
|
4
4
|
import { discloseReminderShaped, observeReminderMarkEcho } from "../core/reminder-disclosure.js";
|
|
5
5
|
import { redactSecrets } from "../core/untrusted-egress.js";
|
|
6
6
|
import { isPrivateHost } from "../core/runner/image.js";
|
|
7
|
+
import { DEFAULT_CHARS_PER_TOKEN } from "../internal/harness.js";
|
|
7
8
|
import { binaryMagicFormat } from "./fs/safety.js";
|
|
8
9
|
import { pdfMagicMatches } from "./fs/pdf.js";
|
|
9
10
|
const MAX_REDIRECTS = 10;
|
|
@@ -701,7 +702,7 @@ export function resolveSummaryInputChars(model, override) {
|
|
|
701
702
|
return Math.floor(override);
|
|
702
703
|
}
|
|
703
704
|
const window = model.contextTokens ?? model.contextWindow;
|
|
704
|
-
const charsPerToken = model.charsPerToken ??
|
|
705
|
+
const charsPerToken = model.charsPerToken ?? DEFAULT_CHARS_PER_TOKEN;
|
|
705
706
|
for (const [name, value] of [
|
|
706
707
|
["contextTokens/contextWindow", window],
|
|
707
708
|
["maxTokens", model.maxTokens],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"description": "Stateless, task-oriented AI agent core",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "rm -rf dist && tsc -p tsconfig.build.json && tsc -p tsconfig.build.json --emitDeclarationOnly --removeComments false",
|
|
50
50
|
"typecheck": "tsc --noEmit",
|
|
51
|
+
"pretest": "npm run gate:debts",
|
|
51
52
|
"test": "vitest run --exclude \"**/*.local.test.ts\"",
|
|
52
53
|
"test:watch": "vitest",
|
|
53
54
|
"gate:skips": "vitest run --exclude \"**/node_modules/**\" --exclude \"**/*.local.test.ts\" --reporter=default --reporter=json --outputFile.json=.skip-report.json && node scripts/verify-skip-baseline.mjs --report .skip-report.json",
|
|
@@ -65,6 +66,9 @@
|
|
|
65
66
|
"gate:single-mint": "node scripts/verify-single-mint.mjs",
|
|
66
67
|
"gate:nuia": "node scripts/verify-nuia-baseline.mjs",
|
|
67
68
|
"gate:field-liveness": "node scripts/verify-field-liveness.mjs",
|
|
69
|
+
"gate:debts": "node scripts/verify-debts.mjs",
|
|
70
|
+
"handoff:diff": "node scripts/dist-handoff-diff.mjs",
|
|
71
|
+
"gate:debts:rc": "node scripts/verify-debts.mjs --rc-gate",
|
|
68
72
|
"gate:error-surface": "node scripts/verify-error-surface.mjs",
|
|
69
73
|
"gate:criteria": "node scripts/criteria-lint.mjs",
|
|
70
74
|
"gate:reminder-literal": "node scripts/verify-reminder-literal.mjs"
|