@sema-agent/core 5.46.0 → 5.48.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 +93 -0
- package/dist/agents/agent-transcript-tool.d.ts +4 -0
- package/dist/agents/agent-transcript-tool.js +10 -3
- package/dist/agents/send-message-tool.d.ts +43 -1
- package/dist/agents/send-message-tool.js +50 -11
- package/dist/agents/subagent.d.ts +18 -0
- package/dist/agents/subagent.js +231 -4
- package/dist/config/defaults.d.ts +20 -0
- package/dist/config/defaults.js +5 -0
- package/dist/core/background-agent-store.d.ts +1 -0
- package/dist/core/background-agent-store.js +13 -0
- package/dist/core/governance-codes.d.ts +13 -0
- package/dist/core/governance-codes.js +33 -0
- package/dist/core/mcp.d.ts +6 -1
- package/dist/core/mcp.js +34 -7
- package/dist/core/memory-engine/delegation-settlement.d.ts +318 -0
- package/dist/core/memory-engine/delegation-settlement.js +661 -0
- package/dist/core/memory-engine/engine.d.ts +159 -1
- package/dist/core/memory-engine/engine.js +699 -15
- package/dist/core/memory-engine/file-backend.d.ts +1 -0
- package/dist/core/memory-engine/file-backend.js +3 -1
- package/dist/core/memory-engine/frontmatter.d.ts +46 -19
- package/dist/core/memory-engine/frontmatter.js +91 -77
- package/dist/core/memory-engine/index.d.ts +4 -3
- package/dist/core/memory-engine/index.js +3 -2
- package/dist/core/memory-engine/layout.d.ts +14 -0
- package/dist/core/memory-engine/layout.js +2 -2
- package/dist/core/memory-engine/memory-backend-contract.js +43 -0
- package/dist/core/memory-engine/origin-clearance.d.ts +66 -0
- package/dist/core/memory-engine/origin-clearance.js +84 -0
- package/dist/core/memory-engine/provenance-wording.d.ts +50 -0
- package/dist/core/memory-engine/provenance-wording.js +15 -0
- package/dist/core/memory-engine/tools.d.ts +61 -7
- package/dist/core/memory-engine/tools.js +34 -9
- package/dist/core/memory-engine/types.d.ts +70 -2
- package/dist/core/reminder-disclosure.d.ts +90 -0
- package/dist/core/reminder-disclosure.js +64 -0
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +6 -0
- package/dist/core/runner/prepare-acquire-reconcile.js +1 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +4 -0
- package/dist/core/runner/prepare-hands-readface.js +1 -0
- package/dist/core/runner/prepare-memory.js +50 -15
- package/dist/core/runner/prepare-task.d.ts +39 -0
- package/dist/core/runner/prepare-task.js +128 -40
- package/dist/core/runner/runtask.js +3 -1
- package/dist/core/session-reconcile.js +3 -2
- package/dist/core/session-store.d.ts +59 -1
- package/dist/core/session-store.js +82 -14
- package/dist/core/session.d.ts +83 -1
- package/dist/core/task-registry-agent.d.ts +28 -0
- package/dist/core/task-registry-agent.js +63 -2
- package/dist/core/task-registry.d.ts +21 -0
- package/dist/core/task-registry.js +4 -1
- package/dist/core/types.d.ts +98 -3
- package/dist/core/types.js +3 -0
- package/dist/core/untrusted-text.d.ts +63 -0
- package/dist/core/untrusted-text.js +48 -0
- package/dist/core/wiring-manifest.d.ts +35 -0
- package/dist/core/wiring-manifest.js +21 -1
- package/dist/engine/harness/types.d.ts +36 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.js +6 -4
- package/dist/internal/harness-types.d.ts +1 -0
- package/dist/stores/file/index.d.ts +19 -3
- package/dist/stores/file/index.js +24 -1
- package/dist/stores/file/session-store.d.ts +18 -4
- package/dist/stores/file/session-store.js +73 -12
- package/dist/tools/fs/fs-pdf.d.ts +12 -1
- package/dist/tools/fs/fs-pdf.js +17 -3
- package/dist/tools/fs/fs-read.d.ts +2 -1
- package/dist/tools/fs/fs-read.js +33 -5
- package/dist/tools/fs/fs-shared.d.ts +6 -2
- package/dist/tools/fs/index.d.ts +7 -0
- package/dist/tools/fs/index.js +1 -1
- package/dist/tools/task-list.d.ts +5 -1
- package/dist/tools/web.js +21 -2
- package/package.json +3 -2
- package/test/export-surface.snapshot.json +24 -2
|
@@ -4,6 +4,9 @@ import type { ToolResultStore } from "../../core/tool-result-store.js";
|
|
|
4
4
|
import { type EvictPolicy } from "../../core/session-store.js";
|
|
5
5
|
import type { SessionStore } from "../../core/session.js";
|
|
6
6
|
import { type FileCheckpointStoreOptions } from "./checkpoint-store.js";
|
|
7
|
+
import type { BackgroundAgentStore } from "../../core/background-agent-store.js";
|
|
8
|
+
import type { MailboxStore } from "../../core/mailbox-store.js";
|
|
9
|
+
import type { RosterStore } from "../../agents/roster-store.js";
|
|
7
10
|
import type { SessionPolicyStore } from "../../core/session-policy-store.js";
|
|
8
11
|
import type { FileSnapshotStore } from "../../core/file-snapshot-store.js";
|
|
9
12
|
import type { WorkflowJournalStore } from "../../core/workflow-journal-store.js";
|
|
@@ -76,9 +79,8 @@ export interface FileStorageBackendOptions {
|
|
|
76
79
|
* Reach TODAY: {@link FileStorageBackend.sessionPolicyStore} (rules read as ABSENT ⇒ an unconstrained
|
|
77
80
|
* task), {@link FileStorageBackend.sessionStore}'s repo (a session listing that silently omits what it
|
|
78
81
|
* could not read) and {@link FileStorageBackend.fileSnapshotStore} (a snapshot/scope read as missing,
|
|
79
|
-
* and a blob GC that aborted). The roster and
|
|
80
|
-
*
|
|
81
|
-
* the same constructor.
|
|
82
|
+
* and a blob GC that aborted). The aggregated roster and mailbox stores receive the same sink. A
|
|
83
|
+
* store that grows the seat later is wired here in the same constructor.
|
|
82
84
|
*
|
|
83
85
|
* The payload is the UNION shape ({@link FileStorageCorruptReadInfo}): `path` + `reason` always,
|
|
84
86
|
* with the session-policy face's `sessionId`/`principal` present only when the reading store keys by
|
|
@@ -125,6 +127,18 @@ export declare class FileStorageBackend {
|
|
|
125
127
|
* `RunnerDeps.usageWindowStore` so a deployment's `usageWindows` survive a restart (an in-memory
|
|
126
128
|
* ledger silently re-grants every allowance on every boot). */
|
|
127
129
|
readonly usageWindowStore: UsageWindowStore;
|
|
130
|
+
/** design/151 S1a / subagent transcript persistence — the durable background-agent execution
|
|
131
|
+
* ledger over the same data root. Wire into BOTH `RunnerDeps.backgroundAgentStore` AND (for a
|
|
132
|
+
* deployment-composed Agent tool) `SubagentToolOptions.background.agentStore` — SAME instance
|
|
133
|
+
* (the RB-37 pairing discipline). Aggregated here so the TOC full-persistence assembly is one
|
|
134
|
+
* constructor, the same reason the session/checkpoint/tool-result trio are. */
|
|
135
|
+
readonly backgroundAgentStore: BackgroundAgentStore;
|
|
136
|
+
/** design/151 §7 S3c — the durable SendMessage mailbox (tier-3 parking lane), same data root.
|
|
137
|
+
* Wire into `RunnerDeps.mailboxStore` (single-instance pairing, same as above). */
|
|
138
|
+
readonly mailboxStore: MailboxStore;
|
|
139
|
+
/** design/147 S1c — the durable name→agent roster (layer 0.5 resolution), `root/roster.json`.
|
|
140
|
+
* Wire into `RunnerDeps.rosterStore` so name-addressing survives a restart alongside the rows. */
|
|
141
|
+
readonly rosterStore: RosterStore;
|
|
128
142
|
/**
|
|
129
143
|
* design/84 Seam B (TOC) — the per-scope consolidation lock for `consolidateScope`'s `acquire` injection
|
|
130
144
|
* point: `consolidateScope(scope, { store: backend.memoryStore, llm, acquire: backend.consolidationLock })`.
|
|
@@ -133,6 +147,8 @@ export declare class FileStorageBackend {
|
|
|
133
147
|
*/
|
|
134
148
|
readonly consolidationLock: (scope: string) => (() => void) | undefined;
|
|
135
149
|
private readonly lock;
|
|
150
|
+
private readonly fileAgentRows;
|
|
151
|
+
private readonly fileMailbox;
|
|
136
152
|
private readonly fileCheckpoints;
|
|
137
153
|
private readonly fileMemory;
|
|
138
154
|
private readonly ttl;
|
|
@@ -11,6 +11,9 @@ import { FileSessionPolicyStore } from "./session-policy-store.js";
|
|
|
11
11
|
import { FileFileSnapshotStore } from "./file-snapshot-store.js";
|
|
12
12
|
import { FileWorkflowJournalStore } from "./workflow-journal-store.js";
|
|
13
13
|
import { FileUsageWindowStore } from "./usage-window-store.js";
|
|
14
|
+
import { FileBackgroundAgentStore } from "./background-agent-store.js";
|
|
15
|
+
import { FileMailboxStore } from "./mailbox-store.js";
|
|
16
|
+
import { FileRosterStore } from "../../agents/roster-store.js";
|
|
14
17
|
export { FileCheckpointStore } from "./checkpoint-store.js";
|
|
15
18
|
export { FileMemoryStore } from "./memory-store.js";
|
|
16
19
|
export { FileSessionRepo } from "./session-store.js";
|
|
@@ -33,8 +36,13 @@ export class FileStorageBackend {
|
|
|
33
36
|
fileSnapshotStore;
|
|
34
37
|
workflowJournalStore;
|
|
35
38
|
usageWindowStore;
|
|
39
|
+
backgroundAgentStore;
|
|
40
|
+
mailboxStore;
|
|
41
|
+
rosterStore;
|
|
36
42
|
consolidationLock;
|
|
37
43
|
lock;
|
|
44
|
+
fileAgentRows;
|
|
45
|
+
fileMailbox;
|
|
38
46
|
fileCheckpoints;
|
|
39
47
|
fileMemory;
|
|
40
48
|
ttl;
|
|
@@ -49,7 +57,7 @@ export class FileStorageBackend {
|
|
|
49
57
|
const corruptRead = opts.onCorruptRead !== undefined ? { onCorruptRead: opts.onCorruptRead } : undefined;
|
|
50
58
|
const repo = new FileSessionRepo(this.root, corruptRead);
|
|
51
59
|
this.fileSessions = repo;
|
|
52
|
-
this.ttl = new TtlSessionStore({ repo, evict: opts.evict ?? "forget", durability: "durable" });
|
|
60
|
+
this.ttl = new TtlSessionStore({ repo, evict: opts.evict ?? "forget", durability: "durable", placements: { subagent: { durability: "durable" } } });
|
|
53
61
|
this.sessionStore = this.ttl;
|
|
54
62
|
this.fileCheckpoints = new FileCheckpointStore(this.root, opts.checkpoint);
|
|
55
63
|
this.checkpointStore = this.fileCheckpoints;
|
|
@@ -61,6 +69,11 @@ export class FileStorageBackend {
|
|
|
61
69
|
this.fileWorkflowJournal = new FileWorkflowJournalStore(this.root);
|
|
62
70
|
this.workflowJournalStore = this.fileWorkflowJournal;
|
|
63
71
|
this.usageWindowStore = new FileUsageWindowStore(this.root);
|
|
72
|
+
this.fileAgentRows = new FileBackgroundAgentStore(this.root);
|
|
73
|
+
this.backgroundAgentStore = this.fileAgentRows;
|
|
74
|
+
this.fileMailbox = new FileMailboxStore(this.root, corruptRead ?? {});
|
|
75
|
+
this.mailboxStore = this.fileMailbox;
|
|
76
|
+
this.rosterStore = new FileRosterStore(join(this.root, "roster.json"), corruptRead ?? {});
|
|
64
77
|
this.consolidationLock = createFileConsolidationLock(join(this.root, "consolidation-locks"));
|
|
65
78
|
}
|
|
66
79
|
catch (err) {
|
|
@@ -87,6 +100,16 @@ export class FileStorageBackend {
|
|
|
87
100
|
}
|
|
88
101
|
this.fileMemory.close();
|
|
89
102
|
this.fileWorkflowJournal.dispose();
|
|
103
|
+
try {
|
|
104
|
+
this.fileAgentRows.close();
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
this.fileMailbox.close();
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
}
|
|
90
113
|
this.lock.release();
|
|
91
114
|
}
|
|
92
115
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { Session, SessionForkOptions, SessionMetadata, SessionRepo, SessionTreeEntry } from "../../internal/harness.js";
|
|
1
|
+
import type { Session, SessionCreateOptions, SessionForkOptions, SessionMetadata, SessionPlacementRecord, SessionRepo, SessionTreeEntry } from "../../internal/harness.js";
|
|
2
|
+
import type { PlacedSessionRow } from "../../core/session.js";
|
|
2
3
|
export interface FileSessionRepoOptions {
|
|
3
4
|
/**
|
|
4
5
|
* Disclosure sink for a durable read this repo treats as ABSENT (ruled 2026-08-03). Same name and
|
|
@@ -47,11 +48,24 @@ export declare class FileSessionRepo implements SessionRepo {
|
|
|
47
48
|
* means a stale holder can never revoke the rebuilt entry.
|
|
48
49
|
*/
|
|
49
50
|
dispose(): Promise<void>;
|
|
50
|
-
create(options?:
|
|
51
|
-
id?: string;
|
|
52
|
-
}): Promise<Session>;
|
|
51
|
+
create(options?: SessionCreateOptions): Promise<Session>;
|
|
53
52
|
open(metadata: SessionMetadata): Promise<Session>;
|
|
54
53
|
list(): Promise<SessionMetadata[]>;
|
|
54
|
+
/**
|
|
55
|
+
* Subagent transcript persistence — the PLACED partition's own enumeration (the joint reap's
|
|
56
|
+
* partition-leg input; `TtlSessionStore` re-exposes it verbatim). Age (`olderThanMs`) is judged on
|
|
57
|
+
* the session FILE's mtime — the transcript's last append — falling back to `placedAt` when the
|
|
58
|
+
* stat fails (mtime is also the CC orphan-adoption window's basis, so the two age reads agree).
|
|
59
|
+
* Rows missing either `(scope, handle)` join half return the honest `tupleIncomplete` variant
|
|
60
|
+
* (consumers fail closed on it — never age-reaped, see the SessionStore contract).
|
|
61
|
+
*/
|
|
62
|
+
listPlaced(kind: "subagent", opts?: {
|
|
63
|
+
olderThanMs?: number;
|
|
64
|
+
scope?: string;
|
|
65
|
+
}): Promise<PlacedSessionRow[]>;
|
|
66
|
+
/** Cheap placement probe for one id (the TtlSessionStore's cold-cache release consults it so the
|
|
67
|
+
* placed real-deletion obligation holds after a restart). `undefined` = missing file OR unplaced. */
|
|
68
|
+
placementOf(sessionId: string): Promise<SessionPlacementRecord | undefined>;
|
|
55
69
|
delete(metadata: SessionMetadata): Promise<void>;
|
|
56
70
|
fork(sourceMetadata: SessionMetadata, options?: SessionForkOptions): Promise<Session>;
|
|
57
71
|
/** 2c session-sync ([275], the FILE half of [266]②): the FULL oldest-first log for export. The file backend
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readdirSync, rmSync } from "node:fs";
|
|
1
|
+
import { existsSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { BaseSessionStorage, StoredSession, SessionError, getEntriesToFork, uuidv7, validateEntriesForImport, } from "../../internal/harness.js";
|
|
4
4
|
import { canonicalStoreKey, AppendLog, atomicWriteFile, ensureDir, readJsonlRecords, sanitizePathComponent } from "./fs-atomic.js";
|
|
@@ -83,18 +83,20 @@ export class FileSessionRepo {
|
|
|
83
83
|
const lines = readJsonlRecords(path, (info) => this.disclose(info.path, info.reason));
|
|
84
84
|
let createdAt = "";
|
|
85
85
|
let forkedFrom;
|
|
86
|
+
let placement;
|
|
86
87
|
const entries = [];
|
|
87
88
|
for (const line of lines) {
|
|
88
89
|
if (line.kind === "meta") {
|
|
89
90
|
createdAt = line.createdAt;
|
|
90
91
|
forkedFrom = line.forkedFrom;
|
|
92
|
+
placement = line.placement;
|
|
91
93
|
}
|
|
92
94
|
else
|
|
93
95
|
entries.push(line.entry);
|
|
94
96
|
}
|
|
95
|
-
return { createdAt, ...(forkedFrom !== undefined ? { forkedFrom } : {}), entries };
|
|
97
|
+
return { createdAt, ...(forkedFrom !== undefined ? { forkedFrom } : {}), ...(placement !== undefined ? { placement } : {}), entries };
|
|
96
98
|
}
|
|
97
|
-
storage(id, createdAt, entries, forkedFrom) {
|
|
99
|
+
storage(id, createdAt, entries, forkedFrom, placement) {
|
|
98
100
|
const canonical = canonicalStoreKey(this.pathFor(id));
|
|
99
101
|
const live = sharedSessionStorages.get(canonical)?.deref();
|
|
100
102
|
if (live !== undefined) {
|
|
@@ -103,7 +105,7 @@ export class FileSessionRepo {
|
|
|
103
105
|
return live;
|
|
104
106
|
}
|
|
105
107
|
const log = new AppendLog(this.pathFor(id));
|
|
106
|
-
const created = new FileSessionStorage(log, { id, createdAt, ...(forkedFrom !== undefined ? { forkedFrom } : {}) }, entries, canonical);
|
|
108
|
+
const created = new FileSessionStorage(log, { id, createdAt, ...(forkedFrom !== undefined ? { forkedFrom } : {}), ...(placement !== undefined ? { placement } : {}) }, entries, canonical);
|
|
107
109
|
sharedSessionStorages.set(canonical, new WeakRef(created));
|
|
108
110
|
created.addHolder(this);
|
|
109
111
|
this.joined.set(canonical, new WeakRef(created));
|
|
@@ -128,15 +130,16 @@ export class FileSessionRepo {
|
|
|
128
130
|
const path = this.pathFor(id);
|
|
129
131
|
if (!existsSync(path)) {
|
|
130
132
|
evictSharedSessionStorage(canonicalStoreKey(path));
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
const placement = options.placement !== undefined ? { ...options.placement, placedAt: options.placement.placedAt ?? Date.now() } : undefined;
|
|
134
|
+
atomicWriteFile(this.tmpDir, path, `${JSON.stringify({ kind: "meta", id, createdAt, ...(placement !== undefined ? { placement } : {}) })}\n`);
|
|
135
|
+
return new StoredSession(this.storage(id, createdAt, [], undefined, placement));
|
|
133
136
|
}
|
|
134
|
-
const { createdAt: existingCreatedAt, forkedFrom, entries } = this.read(id);
|
|
135
|
-
return new StoredSession(this.storage(id, existingCreatedAt || createdAt, entries, forkedFrom));
|
|
137
|
+
const { createdAt: existingCreatedAt, forkedFrom, placement, entries } = this.read(id);
|
|
138
|
+
return new StoredSession(this.storage(id, existingCreatedAt || createdAt, entries, forkedFrom, placement));
|
|
136
139
|
}
|
|
137
140
|
async open(metadata) {
|
|
138
|
-
const { createdAt, forkedFrom, entries } = this.read(metadata.id);
|
|
139
|
-
return new StoredSession(this.storage(metadata.id, createdAt, entries, forkedFrom));
|
|
141
|
+
const { createdAt, forkedFrom, placement, entries } = this.read(metadata.id);
|
|
142
|
+
return new StoredSession(this.storage(metadata.id, createdAt, entries, forkedFrom, placement));
|
|
140
143
|
}
|
|
141
144
|
async list() {
|
|
142
145
|
let names;
|
|
@@ -155,7 +158,9 @@ export class FileSessionRepo {
|
|
|
155
158
|
continue;
|
|
156
159
|
const id = name.slice(0, -SUFFIX.length);
|
|
157
160
|
try {
|
|
158
|
-
const { createdAt, forkedFrom } = this.read(id);
|
|
161
|
+
const { createdAt, forkedFrom, placement } = this.read(id);
|
|
162
|
+
if (placement !== undefined)
|
|
163
|
+
continue;
|
|
159
164
|
out.push({ id, createdAt, ...(forkedFrom !== undefined ? { forkedFrom } : {}) });
|
|
160
165
|
}
|
|
161
166
|
catch (err) {
|
|
@@ -166,12 +171,68 @@ export class FileSessionRepo {
|
|
|
166
171
|
}
|
|
167
172
|
return out.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
168
173
|
}
|
|
174
|
+
async listPlaced(kind, opts) {
|
|
175
|
+
let names;
|
|
176
|
+
try {
|
|
177
|
+
names = readdirSync(this.dir);
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
if (err.code !== "ENOENT") {
|
|
181
|
+
this.disclose(this.dir, `session directory unreadable (${err.code ?? "unknown"}) — placed listing degraded to empty`);
|
|
182
|
+
}
|
|
183
|
+
return [];
|
|
184
|
+
}
|
|
185
|
+
const now = Date.now();
|
|
186
|
+
const out = [];
|
|
187
|
+
for (const name of names) {
|
|
188
|
+
if (!name.endsWith(SUFFIX))
|
|
189
|
+
continue;
|
|
190
|
+
const id = name.slice(0, -SUFFIX.length);
|
|
191
|
+
let placement;
|
|
192
|
+
try {
|
|
193
|
+
placement = this.read(id).placement;
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
if (!(err instanceof SessionError && err.code === "not_found")) {
|
|
197
|
+
this.disclose(this.pathFor(id), `session log unreadable (${err instanceof Error ? err.message : String(err)}) — skipped from the placed listing`);
|
|
198
|
+
}
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (placement === undefined || placement.kind !== kind)
|
|
202
|
+
continue;
|
|
203
|
+
if (opts?.scope !== undefined && placement.scope !== opts.scope)
|
|
204
|
+
continue;
|
|
205
|
+
if (opts?.olderThanMs !== undefined) {
|
|
206
|
+
let ageAnchor = placement.placedAt;
|
|
207
|
+
try {
|
|
208
|
+
ageAnchor = statSync(this.pathFor(id)).mtimeMs;
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
}
|
|
212
|
+
if (now - ageAnchor <= opts.olderThanMs)
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
out.push(placement.scope !== undefined && placement.handle !== undefined
|
|
216
|
+
? { sessionId: id, placedAt: placement.placedAt, scope: placement.scope, handle: placement.handle }
|
|
217
|
+
: { sessionId: id, placedAt: placement.placedAt, tupleIncomplete: true });
|
|
218
|
+
}
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
221
|
+
async placementOf(sessionId) {
|
|
222
|
+
try {
|
|
223
|
+
return this.read(sessionId).placement;
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
169
229
|
async delete(metadata) {
|
|
170
230
|
evictSharedSessionStorage(canonicalStoreKey(this.pathFor(metadata.id)));
|
|
171
231
|
try {
|
|
172
232
|
rmSync(this.pathFor(metadata.id), { force: true });
|
|
173
233
|
}
|
|
174
|
-
catch {
|
|
234
|
+
catch (err) {
|
|
235
|
+
throw new SessionError("storage", `session ${metadata.id} could not be deleted: ${err instanceof Error ? err.message : String(err)}`, err instanceof Error ? err : undefined);
|
|
175
236
|
}
|
|
176
237
|
}
|
|
177
238
|
async fork(sourceMetadata, options = {}) {
|
|
@@ -3,6 +3,17 @@ import type { DocumentContent, ImageContent, TextContent } from "../../internal/
|
|
|
3
3
|
import type { ExecutionEnv } from "../../internal/harness-types.js";
|
|
4
4
|
import { type PdfModelCapabilities } from "./pdf.js";
|
|
5
5
|
import { type ReadImageDownsamplerOption } from "./fs-shared.js";
|
|
6
|
+
import { type ReminderDisclosureCounts } from "../../core/reminder-disclosure.js";
|
|
7
|
+
/** design/319 (B ticket) — the Read tool's disclosure state, threaded into the PDF TEXT legs (the
|
|
8
|
+
* two `pdftotext` extraction arms — the only PDF returns with a model-facing text projection of
|
|
9
|
+
* the document; the native document block and rendered page images have no text to scan). Shares
|
|
10
|
+
* the Read closure's throttle windows so a PDF and its text read dedup on the same file key. */
|
|
11
|
+
export interface PdfReminderDisclosure {
|
|
12
|
+
mark: string | undefined;
|
|
13
|
+
windows: Map<string, number>;
|
|
14
|
+
key: string;
|
|
15
|
+
counts?: ReminderDisclosureCounts;
|
|
16
|
+
}
|
|
6
17
|
export { pdfModelCapabilitiesOf, type PdfModelCapabilities } from "./pdf.js";
|
|
7
18
|
/** What the Read tool's PDF pipeline returns (document block, or rendered page images, or an error string). */
|
|
8
19
|
type ReadPdfReturn = string | {
|
|
@@ -33,7 +44,7 @@ type ReadPdfReturn = string | {
|
|
|
33
44
|
* Every degraded return carries `details.fallback = { level, reason }` (telemetry on the structured frame).
|
|
34
45
|
* `caps` absent ⇒ fully capable (byte-compat: native document block; the brain placeholder still guards).
|
|
35
46
|
*/
|
|
36
|
-
export declare function readPdfFile(env: ExecutionEnv, path: string, key: string, pages: string | undefined, signal: AbortSignal | undefined, downsamplerOpt: ReadImageDownsamplerOption, cwd: string, preRead?: Uint8Array, caps?: PdfModelCapabilities, readDeny?: import("./read-deny.js").ReadDenyMatcher): Promise<ReadPdfReturn>;
|
|
47
|
+
export declare function readPdfFile(env: ExecutionEnv, path: string, key: string, pages: string | undefined, signal: AbortSignal | undefined, downsamplerOpt: ReadImageDownsamplerOption, cwd: string, preRead?: Uint8Array, caps?: PdfModelCapabilities, readDeny?: import("./read-deny.js").ReadDenyMatcher, disclosure?: PdfReminderDisclosure): Promise<ReadPdfReturn>;
|
|
37
48
|
/** E1: readPdfFile's own return type stays `ReadPdfReturn` (its INTERNAL string-means-error dispatch
|
|
38
49
|
* contract, shared with pdfPagesToImageBlocks) — the isError flag is applied once, here, at the tool's
|
|
39
50
|
* actual execute() boundary, not inside the helper. */
|
package/dist/tools/fs/fs-pdf.js
CHANGED
|
@@ -3,6 +3,20 @@ import { delimitUntrusted } from "../../core/untrusted-text.js";
|
|
|
3
3
|
import { MCP_IMAGE_MAX_BASE64 } from "../../core/mcp.js";
|
|
4
4
|
import { PDF_FALLBACK_RENDER_PAGES, PDF_INLINE_PAGE_THRESHOLD, PDF_MAX_EXTRACT_SIZE, PDF_MAX_PAGES_PER_READ, PDF_TARGET_RAW_SIZE, extractPdfPagesAsImages, extractPdfTextLayer, getPdfPageCount, parsePdfPageRange, pdfMagicMatches, } from "./pdf.js";
|
|
5
5
|
import { resolveAutoDownsampler, enoentMessage, MAX_READ_BYTES } from "./fs-shared.js";
|
|
6
|
+
import { discloseReminderShaped } from "../../core/reminder-disclosure.js";
|
|
7
|
+
function pdfDisclosureTrailer(body, d) {
|
|
8
|
+
if (d === undefined)
|
|
9
|
+
return "";
|
|
10
|
+
const out = discloseReminderShaped({
|
|
11
|
+
segments: [body],
|
|
12
|
+
mark: d.mark,
|
|
13
|
+
outlet: "pdf",
|
|
14
|
+
defuseExactMark: false,
|
|
15
|
+
throttle: { key: d.key, windows: d.windows },
|
|
16
|
+
counts: d.counts,
|
|
17
|
+
});
|
|
18
|
+
return out.trailer !== undefined ? `\n${out.trailer}` : "";
|
|
19
|
+
}
|
|
6
20
|
export { pdfModelCapabilitiesOf } from "./pdf.js";
|
|
7
21
|
async function pdfPagesToImageBlocks(pages, path, downsamplerOpt) {
|
|
8
22
|
const downsampler = downsamplerOpt === false ? undefined : (downsamplerOpt ?? (await resolveAutoDownsampler()));
|
|
@@ -30,7 +44,7 @@ function boundPdfExtractedText(text) {
|
|
|
30
44
|
return { body: text, truncated: false };
|
|
31
45
|
return { body: text.slice(0, MAX_READ_BYTES), truncated: true };
|
|
32
46
|
}
|
|
33
|
-
export async function readPdfFile(env, path, key, pages, signal, downsamplerOpt, cwd, preRead, caps, readDeny) {
|
|
47
|
+
export async function readPdfFile(env, path, key, pages, signal, downsamplerOpt, cwd, preRead, caps, readDeny, disclosure) {
|
|
34
48
|
const cap = caps ?? { document: true, vision: true };
|
|
35
49
|
const meta = await env.fileInfo(key, signal);
|
|
36
50
|
if (!meta.ok) {
|
|
@@ -83,7 +97,7 @@ export async function readPdfFile(env, path, key, pages, signal, downsamplerOpt,
|
|
|
83
97
|
{
|
|
84
98
|
type: "text",
|
|
85
99
|
text: `[PDF: ${path} — pages ${range.first}-${last}${pageCount !== undefined ? ` of ${pageCount}` : ""} extracted as text via pdftotext (the serving model does not support native PDF input)${timeoutNote}]` +
|
|
86
|
-
`\n${delimitUntrusted("PDF text layer", body)}${truncated ? `\n[extracted text truncated at ${MAX_READ_BYTES} bytes; narrow the pages range]` : ""}`,
|
|
100
|
+
`\n${delimitUntrusted("PDF text layer", body)}${truncated ? `\n[extracted text truncated at ${MAX_READ_BYTES} bytes; narrow the pages range]` : ""}${pdfDisclosureTrailer(body, disclosure)}`,
|
|
87
101
|
},
|
|
88
102
|
],
|
|
89
103
|
details: {
|
|
@@ -166,7 +180,7 @@ export async function readPdfFile(env, path, key, pages, signal, downsamplerOpt,
|
|
|
166
180
|
{
|
|
167
181
|
type: "text",
|
|
168
182
|
text: `[PDF: ${path} — extracted text layer via pdftotext${pageCount !== undefined ? `; ${pageCount} page${pageCount === 1 ? "" : "s"}` : ""} (the serving model does not support native PDF input)${timeoutNote}]` +
|
|
169
|
-
`\n${delimitUntrusted("PDF text layer", body)}${truncated ? `\n[extracted text truncated at ${MAX_READ_BYTES} bytes; use the pages parameter to read specific ranges]` : ""}`,
|
|
183
|
+
`\n${delimitUntrusted("PDF text layer", body)}${truncated ? `\n[extracted text truncated at ${MAX_READ_BYTES} bytes; use the pages parameter to read specific ranges]` : ""}${pdfDisclosureTrailer(body, disclosure)}`,
|
|
170
184
|
},
|
|
171
185
|
],
|
|
172
186
|
details: {
|
|
@@ -2,7 +2,8 @@ import type { AgentTool, ExecutionEnv } from "../../internal/harness-types.js";
|
|
|
2
2
|
import { type ReadFileState } from "./safety.js";
|
|
3
3
|
import { type PdfModelCapabilities } from "./pdf.js";
|
|
4
4
|
import { type ReadImageDownsamplerOption, type CwdRef } from "./fs-shared.js";
|
|
5
|
+
import { type ReminderDisclosureCounts } from "../../core/reminder-disclosure.js";
|
|
5
6
|
export declare function createReadFileTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: readonly string[], imageDownsampler?: ReadImageDownsamplerOption, pdfCapabilities?: PdfModelCapabilities, bgOutputReadExemption?: (canonicalKey: string, ctx: {
|
|
6
7
|
taskId?: string;
|
|
7
8
|
principal?: string;
|
|
8
|
-
}) => boolean, readCyberReminder?: boolean, readDeny?: import("./read-deny.js").ReadDenyMatcher, readFace?: import("./read-face.js").ReadFace, reminderMark?: string): AgentTool;
|
|
9
|
+
}) => boolean, readCyberReminder?: boolean, readDeny?: import("./read-deny.js").ReadDenyMatcher, readFace?: import("./read-face.js").ReadFace, reminderMark?: string, reminderDisclosureCounts?: ReminderDisclosureCounts): AgentTool;
|
package/dist/tools/fs/fs-read.js
CHANGED
|
@@ -8,8 +8,16 @@ import { PDF_MAX_PAGES_PER_READ, pdfMagicMatches } from "./pdf.js";
|
|
|
8
8
|
import { MAX_READ_BYTES, SLICED_READ_MAX_BYTES, MAX_IMAGE_READ_BYTES, MAX_IMAGE_DOWNSAMPLE_INPUT_BYTES, NO_DOWNSAMPLER_IMAGE_CAP_HINT, resolveAutoDownsampler, MAX_READ_OUTPUT_CHARS, readCyberReminderText, FILE_PATH_PARAMS, countLines, seededFileUnchangedReminder, enoentMessage, } from "./fs-shared.js";
|
|
9
9
|
import { readPdfFile, pdfResultToToolReturn } from "./fs-pdf.js";
|
|
10
10
|
import { openSystemReminder } from "../../core/reminder-mint.js";
|
|
11
|
-
|
|
11
|
+
import { discloseReminderShaped } from "../../core/reminder-disclosure.js";
|
|
12
|
+
export function createReadFileTool(env, state, rootCanonical, cwdRef, additionalRoots, imageDownsampler, pdfCapabilities, bgOutputReadExemption, readCyberReminder, readDeny, readFace, reminderMark, reminderDisclosureCounts) {
|
|
12
13
|
const cyberReminder = readCyberReminder === false ? "" : readCyberReminderText(reminderMark);
|
|
14
|
+
const reminderDisclosureWindows = new Map();
|
|
15
|
+
const pdfReminderDisclosure = (key) => ({
|
|
16
|
+
mark: reminderMark,
|
|
17
|
+
windows: reminderDisclosureWindows,
|
|
18
|
+
key,
|
|
19
|
+
counts: reminderDisclosureCounts,
|
|
20
|
+
});
|
|
13
21
|
const pathBoundLine = readFace === "open"
|
|
14
22
|
? "- `file_path` may be relative (resolved against the tracked working directory) or absolute. Reads are not confined to the workspace roots; a small sensitive-path deny list applies.\n"
|
|
15
23
|
: "- `file_path` may be relative (resolved against the tracked working directory) or absolute (within the configured roots).\n";
|
|
@@ -125,7 +133,7 @@ export function createReadFileTool(env, state, rootCanonical, cwdRef, additional
|
|
|
125
133
|
};
|
|
126
134
|
}
|
|
127
135
|
if (r.key.toLowerCase().endsWith(".pdf")) {
|
|
128
|
-
return pdfResultToToolReturn(await readPdfFile(env, path, r.key, pages, ctx.signal, imageDownsampler, cwdRef?.current ?? rootCanonical, undefined, pdfCapabilities, readDeny));
|
|
136
|
+
return pdfResultToToolReturn(await readPdfFile(env, path, r.key, pages, ctx.signal, imageDownsampler, cwdRef?.current ?? rootCanonical, undefined, pdfCapabilities, readDeny, pdfReminderDisclosure(r.key)));
|
|
129
137
|
}
|
|
130
138
|
const binaryExt = hasBinaryExtension(r.key);
|
|
131
139
|
const binaryExtRefusal = () => errorResult(`Error (Read): "${path}" appears to be a binary file (by extension); this tool reads UTF-8 text only.`);
|
|
@@ -170,7 +178,7 @@ export function createReadFileTool(env, state, rootCanonical, cwdRef, additional
|
|
|
170
178
|
return errorResult(`Error (Read): "${path}" is too large to read in full (${readSize} bytes > ${MAX_READ_BYTES}-byte cap); pass an explicit offset/limit to read a slice, or use grep to search it instead.`);
|
|
171
179
|
}
|
|
172
180
|
if (pdfMagicMatches(readBin.value)) {
|
|
173
|
-
return pdfResultToToolReturn(await readPdfFile(env, path, r.key, pages, ctx.signal, imageDownsampler, cwdRef?.current ?? rootCanonical, readBin.value, pdfCapabilities, readDeny));
|
|
181
|
+
return pdfResultToToolReturn(await readPdfFile(env, path, r.key, pages, ctx.signal, imageDownsampler, cwdRef?.current ?? rootCanonical, readBin.value, pdfCapabilities, readDeny, pdfReminderDisclosure(r.key)));
|
|
174
182
|
}
|
|
175
183
|
const magicFormat = binaryMagicFormat(readBin.value);
|
|
176
184
|
if (magicFormat !== undefined) {
|
|
@@ -225,8 +233,20 @@ export function createReadFileTool(env, state, rootCanonical, cwdRef, additional
|
|
|
225
233
|
}
|
|
226
234
|
state.set(r.key, { hash, totalLines: countLines(content), truncated: false, view: { start: 1, end: total }, lastReadAt: Date.now() });
|
|
227
235
|
const bodyBlocks = rendered.blocks.length > 0 ? rendered.blocks : [{ type: "text", text: "[notebook has 0 cells]" }];
|
|
236
|
+
const nbDisclosure = discloseReminderShaped({
|
|
237
|
+
segments: rendered.blocks.filter((b) => b.type === "text").map((b) => b.text),
|
|
238
|
+
mark: reminderMark,
|
|
239
|
+
outlet: "notebook",
|
|
240
|
+
defuseExactMark: false,
|
|
241
|
+
throttle: { key: r.key, windows: reminderDisclosureWindows },
|
|
242
|
+
counts: reminderDisclosureCounts,
|
|
243
|
+
});
|
|
228
244
|
return {
|
|
229
|
-
content:
|
|
245
|
+
content: [
|
|
246
|
+
...bodyBlocks,
|
|
247
|
+
...(cyberReminder ? [{ type: "text", text: cyberReminder }] : []),
|
|
248
|
+
...(nbDisclosure.trailer !== undefined ? [{ type: "text", text: nbDisclosure.trailer }] : []),
|
|
249
|
+
],
|
|
230
250
|
details: { type: "notebook", file: { filePath: path, cells: parsed.cells.map(stripNotebookImageData) } },
|
|
231
251
|
};
|
|
232
252
|
}
|
|
@@ -291,8 +311,16 @@ export function createReadFileTool(env, state, rootCanonical, cwdRef, additional
|
|
|
291
311
|
if (total === 0)
|
|
292
312
|
return `${openSystemReminder(reminderMark)}Warning: the file exists but the contents are empty.</system-reminder>`;
|
|
293
313
|
const header = pageMarker ?? (truncated ? `[${path}: lines ${start}-${end} of ${total}${end < total ? " — use offset to see more" : ""}]\n` : "");
|
|
314
|
+
const disclosure = discloseReminderShaped({
|
|
315
|
+
segments: [body],
|
|
316
|
+
mark: reminderMark,
|
|
317
|
+
outlet: "read",
|
|
318
|
+
defuseExactMark: false,
|
|
319
|
+
throttle: { key: r.key, windows: reminderDisclosureWindows },
|
|
320
|
+
counts: reminderDisclosureCounts,
|
|
321
|
+
});
|
|
294
322
|
return {
|
|
295
|
-
content: `${nbFallbackPrefix}${header}${body}${cyberReminder}`,
|
|
323
|
+
content: `${nbFallbackPrefix}${header}${body}${cyberReminder}${disclosure.trailer !== undefined ? `\n${disclosure.trailer}` : ""}`,
|
|
296
324
|
details: {
|
|
297
325
|
type: "text",
|
|
298
326
|
file: {
|
|
@@ -140,8 +140,12 @@ export declare const MAX_READ_OUTPUT_CHARS = 100000;
|
|
|
140
140
|
* COVERAGE, stated because the switch is easy to over-read: the reminder rides the plain-text read and
|
|
141
141
|
* the notebook projection. PDF text extraction returns through its own result builder and has never
|
|
142
142
|
* carried it, so extracted PDF text reaches the model without this mitigation whatever the switch says.
|
|
143
|
-
* That gap predates the switch
|
|
144
|
-
*
|
|
143
|
+
* That gap predates the switch; its IMPERSONATION half is now closed — design/319 (B ticket) gave the
|
|
144
|
+
* PDF text legs the same detect-and-disclose trailer as this lane (reminder-shaped bytes in extracted
|
|
145
|
+
* PDF text are disclosed at the outlet, fs-pdf.ts), and their bodies were already fenced. The CYBER
|
|
146
|
+
* half (this malware-alertness sentence) still does not ride PDF text and stays an open item rather
|
|
147
|
+
* than closed silently — widening it to a third surface is a change to what every PDF read costs,
|
|
148
|
+
* not a wiring fix.
|
|
145
149
|
*
|
|
146
150
|
* design/319 (A ticket): a FUNCTION over the session mark (formerly the `READ_CYBER_REMINDER`
|
|
147
151
|
* constant) — the open tag is rendered by the mint home so it carries the run's provenance mark;
|
package/dist/tools/fs/index.d.ts
CHANGED
|
@@ -161,6 +161,13 @@ export interface HandsToolkitOptions {
|
|
|
161
161
|
* the honest form). The tag is rendered by the mint home (core/reminder-mint.ts) — the content
|
|
162
162
|
* BODY next to it is byte-untouched. */
|
|
163
163
|
reminderMark?: string;
|
|
164
|
+
/** design/319 (B ticket) — the per-run trailer/defuse trigger counters (observation seat, G9②):
|
|
165
|
+
* the Read text/notebook/PDF-text disclosure trailers bump `read.*` / `notebook.*` / `pdf.*`
|
|
166
|
+
* keys here (appended / marked / bare_throttled). Threaded by prepare-task, which folds the
|
|
167
|
+
* non-zero result into `TaskResult.stats.mechanisms.reminderDisclosures`; a library-direct
|
|
168
|
+
* mount may pass its own object or omit it (counting off — and the whole disclosure pipeline
|
|
169
|
+
* is off anyway when `reminderMark` is absent). */
|
|
170
|
+
reminderDisclosureCounts?: import("../../core/reminder-disclosure.js").ReminderDisclosureCounts;
|
|
164
171
|
}
|
|
165
172
|
/**
|
|
166
173
|
* Build the per-task hand tool band over an injected env + fresh per-task read state (design/44 §11 A).
|
package/dist/tools/fs/index.js
CHANGED
|
@@ -46,7 +46,7 @@ export function createHandsToolkit(env, readFileState, rootCanonical, opts = {})
|
|
|
46
46
|
onDeploymentClamp: () => deliverEngineNotice(opts.onNotice, deploymentReadFaceClampNotice()),
|
|
47
47
|
});
|
|
48
48
|
const tools = [
|
|
49
|
-
createReadFileTool(env, readFileState, rootCanonical, readOnly ? undefined : cwdRef, readFaceRoots, opts.readImageDownsampler, opts.pdfModelCapabilities, bgOutputReadExemption, opts.readCyberReminder, readDeny, readFace, opts.reminderMark),
|
|
49
|
+
createReadFileTool(env, readFileState, rootCanonical, readOnly ? undefined : cwdRef, readFaceRoots, opts.readImageDownsampler, opts.pdfModelCapabilities, bgOutputReadExemption, opts.readCyberReminder, readDeny, readFace, opts.reminderMark, opts.reminderDisclosureCounts),
|
|
50
50
|
];
|
|
51
51
|
if (!readOnly) {
|
|
52
52
|
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));
|
|
@@ -43,7 +43,11 @@ export interface TaskListItem {
|
|
|
43
43
|
* process ({@link createTaskListTools} keeps a per-store promise chain) — a store shared across
|
|
44
44
|
* PROCESSES additionally needs its own transactional guarantees for those sequences, which is the
|
|
45
45
|
* deployment's half (documented, not solved here; CC's file-backed team list has the same window).
|
|
46
|
-
* - `allocateId` is store-owned so concurrent teammates never mint colliding ids
|
|
46
|
+
* - `allocateId` is store-owned so concurrent teammates never mint colliding ids, and the
|
|
47
|
+
* high-water mark is MONOTONIC ACROSS REOPENS for any backend that outlives one wrapper
|
|
48
|
+
* instance: a restart/reconnect must never re-mint an id the backend has already handed out
|
|
49
|
+
* (the conformance suite's durable tier pins exactly this — a counter that resets while the
|
|
50
|
+
* rows survive makes every receipt lie and every subsequent update miss).
|
|
47
51
|
* - Value semantics: `get`/`list` return detached copies; `set` stores a detached copy. Callers
|
|
48
52
|
* mutate their copy and write it back — a live-reference store would alias already-emitted events.
|
|
49
53
|
* - RB-274: `list` is ordered by ID (numeric ascending), not by write order. The tool lane always
|
package/dist/tools/web.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { defineTool, errorResult } from "../core/tools.js";
|
|
3
3
|
import { delimitUntrusted, inlineUntrusted } from "../core/untrusted-text.js";
|
|
4
|
+
import { discloseReminderShaped } from "../core/reminder-disclosure.js";
|
|
4
5
|
import { redactSecrets } from "../core/untrusted-egress.js";
|
|
5
6
|
import { isPrivateHost } from "../core/runner/image.js";
|
|
6
7
|
import { binaryMagicFormat } from "./fs/safety.js";
|
|
@@ -638,7 +639,17 @@ export function webFetchToolSpec(config = {}) {
|
|
|
638
639
|
const partialNote = bodyCut
|
|
639
640
|
? `\n\n[WebFetch: ${cutPhrase} — the content above is PARTIAL: ${bodyBytes?.length ?? 0} bytes were received before the cutoff and the tail is missing. Treat absent information as unfetched, not absent from the source.]`
|
|
640
641
|
: "";
|
|
641
|
-
|
|
642
|
+
let modelText = withNote + truncationNote + partialNote + sourceEcho;
|
|
643
|
+
{
|
|
644
|
+
const d = discloseReminderShaped({
|
|
645
|
+
segments: [modelText],
|
|
646
|
+
mark: ctx.reminderMark,
|
|
647
|
+
outlet: "webFetch",
|
|
648
|
+
defuseExactMark: true,
|
|
649
|
+
...(ctx.reminderDisclosureCounts !== undefined ? { counts: ctx.reminderDisclosureCounts } : {}),
|
|
650
|
+
});
|
|
651
|
+
modelText = d.trailer !== undefined ? `${d.segments[0]}\n\n${d.trailer}` : d.segments[0];
|
|
652
|
+
}
|
|
642
653
|
const RESULT_PREVIEW_CHARS = 8_000;
|
|
643
654
|
return {
|
|
644
655
|
content: modelText,
|
|
@@ -947,7 +958,15 @@ export function createWebSearchTool(config) {
|
|
|
947
958
|
const tailReminder = shown.length > 0
|
|
948
959
|
? "REMINDER: You MUST include the sources above in your response to the user using markdown hyperlinks."
|
|
949
960
|
: "REMINDER: this search returned ZERO usable sources — there is nothing above to cite. Do not fabricate sources, URLs or citations; tell the user the search returned no usable results (the notes above say why, when there is a why) and answer from what you already know, or try a different query.";
|
|
950
|
-
const
|
|
961
|
+
const searchDisclosure = discloseReminderShaped({
|
|
962
|
+
segments: [fenced],
|
|
963
|
+
mark: ctx.reminderMark,
|
|
964
|
+
outlet: "webSearch",
|
|
965
|
+
defuseExactMark: true,
|
|
966
|
+
...(ctx.reminderDisclosureCounts !== undefined ? { counts: ctx.reminderDisclosureCounts } : {}),
|
|
967
|
+
});
|
|
968
|
+
const searchDisclosureNote = searchDisclosure.trailer !== undefined ? `\n\n${searchDisclosure.trailer}` : "";
|
|
969
|
+
const modelText = `${searchDisclosure.segments[0]}${dropNote}${domainNote}${maxResultsNote}${schemeNote}${clipNote}${searchDisclosureNote}\n\n${tailReminder}`;
|
|
951
970
|
return {
|
|
952
971
|
content: modelText,
|
|
953
972
|
details: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/core",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.48.0",
|
|
4
4
|
"description": "Stateless, task-oriented AI agent core",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -66,7 +66,8 @@
|
|
|
66
66
|
"gate:nuia": "node scripts/verify-nuia-baseline.mjs",
|
|
67
67
|
"gate:field-liveness": "node scripts/verify-field-liveness.mjs",
|
|
68
68
|
"gate:error-surface": "node scripts/verify-error-surface.mjs",
|
|
69
|
-
"gate:criteria": "node scripts/criteria-lint.mjs"
|
|
69
|
+
"gate:criteria": "node scripts/criteria-lint.mjs",
|
|
70
|
+
"gate:reminder-literal": "node scripts/verify-reminder-literal.mjs"
|
|
70
71
|
},
|
|
71
72
|
"dependencies": {
|
|
72
73
|
"@modelcontextprotocol/sdk": "1.30.0",
|