@inixiative/foundry-core 0.5.9 → 0.5.11
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/package.json +4 -4
- package/src/action-prompt.ts +11 -2
- package/src/adapters/file-memory.ts +5 -0
- package/src/adapters/http-memory.ts +5 -0
- package/src/adapters/markdown-docs.ts +12 -1
- package/src/adapters/markdown-hierarchy.ts +7 -2
- package/src/adapters/sqlite-memory.ts +21 -6
- package/src/base-agent.ts +5 -0
- package/src/bounded-set.ts +5 -0
- package/src/cache-lifecycle.ts +5 -0
- package/src/capability.ts +5 -0
- package/src/clarifier.ts +5 -0
- package/src/classifier.ts +5 -0
- package/src/context-layer.ts +10 -0
- package/src/context-stack.ts +5 -0
- package/src/credentials.ts +5 -0
- package/src/decider.ts +5 -0
- package/src/delivery-evidence.ts +5 -0
- package/src/event-stream.ts +5 -0
- package/src/executor.ts +17 -4
- package/src/feedback/archive-turns.ts +5 -0
- package/src/feedback/correction.ts +48 -2
- package/src/feedback/experiment-gate.ts +5 -0
- package/src/feedback/review-findings.ts +5 -0
- package/src/feedback/warden-labels.ts +5 -0
- package/src/feedback/warden-prefilter.ts +5 -0
- package/src/feedback/warden-probes.ts +5 -0
- package/src/harness.ts +7 -2
- package/src/hydrator.ts +5 -0
- package/src/id.ts +5 -0
- package/src/index.ts +25 -12
- package/src/intervention.ts +5 -0
- package/src/messages.ts +5 -0
- package/src/middleware.ts +5 -0
- package/src/model-registry.ts +92 -2
- package/src/native-evidence.ts +18 -0
- package/src/native-tools.ts +5 -0
- package/src/retry.ts +5 -0
- package/src/router.ts +5 -0
- package/src/scope.ts +5 -0
- package/src/serialized-queue.ts +35 -0
- package/src/session-delivery.ts +5 -0
- package/src/signal.ts +5 -0
- package/src/thread.ts +196 -21
- package/src/token-counts.ts +5 -0
- package/src/token-tracker.ts +49 -24
- package/src/tools.ts +6 -1
- package/src/trace.ts +5 -0
- package/src/types.ts +5 -2
- package/src/hooks.ts +0 -133
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inixiative/foundry-core",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
"access": "public"
|
|
31
31
|
},
|
|
32
32
|
"engines": {
|
|
33
|
-
"bun": ">=1.4.
|
|
33
|
+
"bun": ">=1.4.4"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@inixiative/archive": "^0.
|
|
36
|
+
"@inixiative/archive": "^0.33.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependenciesMeta": {
|
|
39
39
|
"@inixiative/archive": {
|
|
@@ -41,6 +41,6 @@
|
|
|
41
41
|
}
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@inixiative/archive": "^0.
|
|
44
|
+
"@inixiative/archive": "^0.33.1"
|
|
45
45
|
}
|
|
46
46
|
}
|
package/src/action-prompt.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @atlas
|
|
3
|
+
* @kind service
|
|
4
|
+
* @partOf primitive:signals
|
|
5
|
+
*/
|
|
6
|
+
import { newId } from './id';
|
|
1
7
|
// ---------------------------------------------------------------------------
|
|
2
8
|
// ActionPrompt — blocking agent→human interaction primitive
|
|
3
9
|
// ---------------------------------------------------------------------------
|
|
@@ -30,6 +36,8 @@ export interface ActionResolution {
|
|
|
30
36
|
readonly by: 'human' | 'policy' | 'timeout';
|
|
31
37
|
readonly action: string; // "approved", "rejected", or an option ID
|
|
32
38
|
readonly input?: string; // for "input" kind prompts
|
|
39
|
+
/** Context the user typed in the message box and sent with this answer. */
|
|
40
|
+
readonly note?: string;
|
|
33
41
|
readonly timestamp: number;
|
|
34
42
|
}
|
|
35
43
|
|
|
@@ -129,7 +137,7 @@ export class ActionQueue {
|
|
|
129
137
|
* Returns the resolution (human choice, policy decision, or timeout).
|
|
130
138
|
*/
|
|
131
139
|
async prompt(opts: PromptOpts): Promise<ActionResolution> {
|
|
132
|
-
const id =
|
|
140
|
+
const id = newId('prompt');
|
|
133
141
|
|
|
134
142
|
const prompt: ActionPrompt = {
|
|
135
143
|
id,
|
|
@@ -197,7 +205,7 @@ export class ActionQueue {
|
|
|
197
205
|
resolve(
|
|
198
206
|
promptId: string,
|
|
199
207
|
action: string,
|
|
200
|
-
opts?: { by?: 'human' | 'policy'; input?: string },
|
|
208
|
+
opts?: { by?: 'human' | 'policy'; input?: string; note?: string },
|
|
201
209
|
): boolean {
|
|
202
210
|
const prompt = this._prompts.get(promptId);
|
|
203
211
|
if (!prompt || prompt.status !== 'pending') return false;
|
|
@@ -206,6 +214,7 @@ export class ActionQueue {
|
|
|
206
214
|
by: opts?.by ?? 'human',
|
|
207
215
|
action,
|
|
208
216
|
input: opts?.input,
|
|
217
|
+
...(opts?.note ? { note: opts.note } : {}),
|
|
209
218
|
timestamp: Date.now(),
|
|
210
219
|
};
|
|
211
220
|
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @atlas
|
|
3
|
+
* @kind adapter
|
|
4
|
+
* @partOf primitive:memory
|
|
5
|
+
*/
|
|
1
6
|
import { existsSync, realpathSync } from 'node:fs';
|
|
2
7
|
import { realpath, stat } from 'node:fs/promises';
|
|
3
8
|
import { homedir } from 'node:os';
|
|
@@ -288,7 +293,13 @@ export function secretInstructionPath(path: string, trusted: readonly string[] =
|
|
|
288
293
|
name.startsWith('.') ||
|
|
289
294
|
/^id_[a-z0-9]+/i.test(name) ||
|
|
290
295
|
/\.(pem|key|p12|pfx|keystore|jks)$/i.test(name) ||
|
|
291
|
-
rest.split(sep).some((part) => CREDENTIAL_DIRECTORIES.has(part))
|
|
296
|
+
rest.split(sep).some((part) => CREDENTIAL_DIRECTORIES.has(part)) ||
|
|
297
|
+
// A `.private` path is never read (FEAT-005): a segment named or ending `.private`, or holding `.private.`.
|
|
298
|
+
path
|
|
299
|
+
.split(sep)
|
|
300
|
+
.some(
|
|
301
|
+
(part) => part === '.private' || part.endsWith('.private') || part.includes('.private.'),
|
|
302
|
+
)
|
|
292
303
|
);
|
|
293
304
|
}
|
|
294
305
|
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @atlas
|
|
3
|
+
* @kind adapter
|
|
4
|
+
* @partOf feature:composition
|
|
5
|
+
*/
|
|
2
6
|
import { readFileSync, realpathSync, statSync } from 'node:fs';
|
|
3
7
|
import { basename, dirname, isAbsolute, join, resolve, sep } from 'node:path';
|
|
8
|
+
import { sha256Hex } from '../context-layer';
|
|
4
9
|
import {
|
|
5
10
|
fenceSegments,
|
|
6
11
|
INSTRUCTION_FILE_LIMIT,
|
|
@@ -685,7 +690,7 @@ export async function discoverHierarchy(
|
|
|
685
690
|
return { files, duplicates, refused, probes: [...probes] };
|
|
686
691
|
}
|
|
687
692
|
|
|
688
|
-
const digest =
|
|
693
|
+
const digest = sha256Hex;
|
|
689
694
|
|
|
690
695
|
/**
|
|
691
696
|
* Blank lines, trailing whitespace, line endings and runs of spaces inside a line do not make two
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @atlas
|
|
3
|
+
* @kind adapter
|
|
4
|
+
* @partOf primitive:memory
|
|
5
|
+
*/
|
|
1
6
|
import { Database } from 'bun:sqlite';
|
|
2
7
|
import type { ContextSource } from '../context-layer';
|
|
3
8
|
import type { ContextRef, HydrationAdapter } from '../hydrator';
|
|
@@ -71,7 +76,7 @@ export class SqliteMemory {
|
|
|
71
76
|
|
|
72
77
|
/** Read by id. */
|
|
73
78
|
get(id: string): SqliteEntry | undefined {
|
|
74
|
-
const row = this.db.query('SELECT * FROM entries WHERE id = ?').get(id) as
|
|
79
|
+
const row = this.db.query('SELECT * FROM entries WHERE id = ?').get(id) as SqliteRow | null;
|
|
75
80
|
return row ? this._rowToEntry(row) : undefined;
|
|
76
81
|
}
|
|
77
82
|
|
|
@@ -81,7 +86,7 @@ export class SqliteMemory {
|
|
|
81
86
|
? this.db.query('SELECT * FROM entries WHERE kind = ? ORDER BY timestamp DESC')
|
|
82
87
|
: this.db.query('SELECT * FROM entries ORDER BY timestamp DESC');
|
|
83
88
|
const rows = kind ? query.all(kind) : query.all();
|
|
84
|
-
return (rows as
|
|
89
|
+
return (rows as SqliteRow[]).map(this._rowToEntry);
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
/** Full-text search. */
|
|
@@ -91,7 +96,7 @@ export class SqliteMemory {
|
|
|
91
96
|
`SELECT entries.* FROM entries_fts JOIN entries ON entries_fts.id = entries.id WHERE entries_fts MATCH ? ORDER BY rank LIMIT ?`,
|
|
92
97
|
)
|
|
93
98
|
.all(query, limit);
|
|
94
|
-
return (rows as
|
|
99
|
+
return (rows as SqliteRow[]).map(this._rowToEntry);
|
|
95
100
|
}
|
|
96
101
|
|
|
97
102
|
/** Delete by id. */
|
|
@@ -105,7 +110,7 @@ export class SqliteMemory {
|
|
|
105
110
|
const query = kind
|
|
106
111
|
? this.db.query('SELECT COUNT(*) as c FROM entries WHERE kind = ?')
|
|
107
112
|
: this.db.query('SELECT COUNT(*) as c FROM entries');
|
|
108
|
-
const row = (kind ? query.get(kind) : query.get()) as
|
|
113
|
+
const row = (kind ? query.get(kind) : query.get()) as { c: number };
|
|
109
114
|
return row.c;
|
|
110
115
|
}
|
|
111
116
|
|
|
@@ -115,7 +120,7 @@ export class SqliteMemory {
|
|
|
115
120
|
? this.db.query('SELECT * FROM entries WHERE kind = ? ORDER BY timestamp DESC LIMIT ?')
|
|
116
121
|
: this.db.query('SELECT * FROM entries ORDER BY timestamp DESC LIMIT ?');
|
|
117
122
|
const rows = kind ? query.all(kind, limit) : query.all(limit);
|
|
118
|
-
return (rows as
|
|
123
|
+
return (rows as SqliteRow[]).map(this._rowToEntry);
|
|
119
124
|
}
|
|
120
125
|
|
|
121
126
|
/** Create a ContextSource that loads entries as context. */
|
|
@@ -168,7 +173,7 @@ export class SqliteMemory {
|
|
|
168
173
|
this.db.close();
|
|
169
174
|
}
|
|
170
175
|
|
|
171
|
-
private _rowToEntry(row:
|
|
176
|
+
private _rowToEntry(row: SqliteRow): SqliteEntry {
|
|
172
177
|
return {
|
|
173
178
|
id: row.id,
|
|
174
179
|
kind: row.kind,
|
|
@@ -180,6 +185,16 @@ export class SqliteMemory {
|
|
|
180
185
|
}
|
|
181
186
|
}
|
|
182
187
|
|
|
188
|
+
/** A row of the `entries` table as bun:sqlite returns it. */
|
|
189
|
+
interface SqliteRow {
|
|
190
|
+
id: string;
|
|
191
|
+
kind: string;
|
|
192
|
+
content: string;
|
|
193
|
+
source: string | null;
|
|
194
|
+
timestamp: number;
|
|
195
|
+
meta: string | null;
|
|
196
|
+
}
|
|
197
|
+
|
|
183
198
|
export interface SqliteEntry {
|
|
184
199
|
readonly id: string;
|
|
185
200
|
readonly kind: string;
|
package/src/base-agent.ts
CHANGED
package/src/bounded-set.ts
CHANGED
package/src/cache-lifecycle.ts
CHANGED
package/src/capability.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @atlas
|
|
3
|
+
* @kind helper
|
|
4
|
+
* @partOf primitive:agents, primitive:tools
|
|
5
|
+
*/
|
|
1
6
|
// ---------------------------------------------------------------------------
|
|
2
7
|
// Capability — permission flags and gating for dangerous operations
|
|
3
8
|
// ---------------------------------------------------------------------------
|
package/src/clarifier.ts
CHANGED
package/src/classifier.ts
CHANGED
package/src/context-layer.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @atlas
|
|
3
|
+
* @kind service
|
|
4
|
+
* @partOf primitive:contextLayers
|
|
5
|
+
*/
|
|
1
6
|
import type { OwnershipScope } from './scope';
|
|
2
7
|
|
|
3
8
|
/**
|
|
@@ -354,6 +359,11 @@ export function computeHash(content: string): string {
|
|
|
354
359
|
return Bun.hash(content).toString(16).slice(0, 16);
|
|
355
360
|
}
|
|
356
361
|
|
|
362
|
+
/** SHA-256 as hex: for content addresses and digests that are persisted or compared across processes (computeHash is a fast in-memory cache key, not this). */
|
|
363
|
+
export function sha256Hex(content: string | Uint8Array): string {
|
|
364
|
+
return new Bun.CryptoHasher('sha256').update(content).digest('hex');
|
|
365
|
+
}
|
|
366
|
+
|
|
357
367
|
export class ContextLayer {
|
|
358
368
|
readonly id: string;
|
|
359
369
|
readonly owner?: OwnershipScope;
|
package/src/context-stack.ts
CHANGED
package/src/credentials.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @atlas
|
|
3
|
+
* @kind helper
|
|
4
|
+
* @partOf primitive:secrets
|
|
5
|
+
*/
|
|
1
6
|
/** Credential references belong in configuration; secrets stay behind the resolver. */
|
|
2
7
|
/** `kingdom-signet` names the Signet a paired Kingdom granted, by the destination's origin plus the owner it was paired as. */
|
|
3
8
|
export type CredentialReference =
|
package/src/decider.ts
CHANGED
package/src/delivery-evidence.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @atlas
|
|
3
|
+
* @kind helper
|
|
4
|
+
* @partOf primitive:nativeSessions
|
|
5
|
+
*/
|
|
1
6
|
/** Portable SHA-256 for public evidence, shared by Bun and the historical browser.
|
|
2
7
|
* This is an integrity join, not a signature or native delivery acknowledgment. */
|
|
3
8
|
export function evidenceDigest(text: string): string {
|
package/src/event-stream.ts
CHANGED
package/src/executor.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @atlas
|
|
3
|
+
* @kind agent
|
|
4
|
+
* @partOf primitive:agents
|
|
5
|
+
*/
|
|
1
6
|
import { type AgentConfig, BaseAgent, type ExecutionResult } from './base-agent';
|
|
2
7
|
import { computeHash, REQUIRED_CONTEXT_BLOCKED } from './context-layer';
|
|
3
|
-
import type { AssembledContext, ContextStack, LayerFilter } from './context-stack';
|
|
8
|
+
import type { AssembledContext, ContextStack, LayerFilter, PromptBlock } from './context-stack';
|
|
4
9
|
import { buildInjectionArtifact, type InjectionArtifact, type MessageDecoration } from './messages';
|
|
5
10
|
import type { NativeEvidence, NativeObservation } from './native-evidence';
|
|
6
11
|
import {
|
|
@@ -52,6 +57,16 @@ export interface ExecuteMeta {
|
|
|
52
57
|
images?: readonly ImageInput[];
|
|
53
58
|
}
|
|
54
59
|
|
|
60
|
+
/**
|
|
61
|
+
* The user's message as a turn sends it: the after-message notes (Wardens chiming in) appended to
|
|
62
|
+
* its end below a rule, each note's text in order. No notes: the message as typed.
|
|
63
|
+
*/
|
|
64
|
+
export function withAfterMessageNotes(message: string, notes: readonly PromptBlock[]): string {
|
|
65
|
+
return notes.length
|
|
66
|
+
? `${message}\n\n---\n\n${notes.map((note) => note.text).join('\n\n')}`
|
|
67
|
+
: message;
|
|
68
|
+
}
|
|
69
|
+
|
|
55
70
|
export type ExecuteHandler<TPayload, TResult> = (
|
|
56
71
|
context: string,
|
|
57
72
|
payload: TPayload,
|
|
@@ -214,9 +229,7 @@ export class Executor<TPayload = unknown, TResult = unknown> extends BaseAgent<T
|
|
|
214
229
|
let recording = true;
|
|
215
230
|
try {
|
|
216
231
|
const sent = (
|
|
217
|
-
|
|
218
|
-
? `${payload}\n\n---\n\n${notes.map((note) => note.text).join('\n\n')}`
|
|
219
|
-
: payload
|
|
232
|
+
typeof payload === 'string' ? withAfterMessageNotes(payload, notes) : payload
|
|
220
233
|
) as TPayload;
|
|
221
234
|
output = await this._handler(context, sent, {
|
|
222
235
|
...meta,
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @atlas
|
|
3
|
+
* @kind transformer
|
|
4
|
+
* @partOf feature:feedback, integration:archive
|
|
5
|
+
*/
|
|
1
6
|
import type { ArchiveSnapshot } from '@inixiative/archive';
|
|
2
7
|
import { typedPrompt } from '@inixiative/archive/import';
|
|
3
8
|
import type { TranscriptTurn } from './correction';
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @atlas
|
|
3
|
+
* @kind service
|
|
4
|
+
* @partOf feature:feedback
|
|
5
|
+
*/
|
|
1
6
|
import { createHash } from 'node:crypto';
|
|
2
7
|
|
|
3
8
|
// The Prospector's correction detection (moved from Oracle's session miner), shared by Oracle's
|
|
@@ -216,8 +221,12 @@ export function extractCorrections(
|
|
|
216
221
|
return { corrections, secretsScrubbed };
|
|
217
222
|
}
|
|
218
223
|
|
|
219
|
-
|
|
220
|
-
|
|
224
|
+
/**
|
|
225
|
+
* Credentials by their own shape (a provider's token prefix, a key block): specific enough that a
|
|
226
|
+
* match is a secret. Foundry lifts these from a message even when the person forgot `$$` (FEAT-005).
|
|
227
|
+
*/
|
|
228
|
+
const SHAPED_SECRETS: ReadonlyArray<readonly [RegExp, string]> = [
|
|
229
|
+
[/sk-ant-[A-Za-z0-9_-]{20,}/g, '[REDACTED:anthropic-key]'],
|
|
221
230
|
[/sk-[A-Za-z0-9_-]{20,}/g, '[REDACTED:api-key]'],
|
|
222
231
|
[/gh[pousr]_[A-Za-z0-9]{20,}/g, '[REDACTED:github-token]'],
|
|
223
232
|
[/github_pat_[A-Za-z0-9_]{20,}/g, '[REDACTED:github-token]'],
|
|
@@ -231,8 +240,19 @@ const SECRET_PATTERNS: ReadonlyArray<readonly [RegExp, string]> = [
|
|
|
231
240
|
[/eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g, '[REDACTED:jwt]'],
|
|
232
241
|
[/\b[a-z]+_(?:live|test|prod|runtime)_[A-Za-z0-9]{16,}/g, '[REDACTED:token]'],
|
|
233
242
|
[/\b(?:npm_|glpat-|hf_)[A-Za-z0-9_-]{20,}/g, '[REDACTED:token]'],
|
|
243
|
+
// Kingdom's run, refresh and Signet access tokens: the prefix and exactly 43 base64url characters.
|
|
244
|
+
[/\bkingdom_(?:run_|refresh_)?[A-Za-z0-9_-]{43}(?![A-Za-z0-9_-])/g, '[REDACTED:kingdom-token]'],
|
|
245
|
+
// API tokens Kingdom, template and Tribe issue: `<environment>_<owner model>_<48 hex>`.
|
|
246
|
+
[
|
|
247
|
+
/\b(?:local|pr|staging|prod|test)_(?:user|org|orgUser|space|spaceUser|integration)_[0-9a-f]{48}\b/g,
|
|
248
|
+
'[REDACTED:api-token]',
|
|
249
|
+
],
|
|
234
250
|
[/hooks\.slack\.com\/services\/[A-Za-z0-9/]+/g, '[REDACTED:slack-webhook]'],
|
|
235
251
|
[/\bASIA[A-Z0-9]{16}/g, '[REDACTED:aws-key]'],
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
/** Credentials by context (an assignment, a header, a URL): a match may be a secret, so it is scrubbed but never lifted. */
|
|
255
|
+
const CONTEXT_SECRETS: ReadonlyArray<readonly [RegExp, string]> = [
|
|
236
256
|
[/\b(Bearer|Basic|DPoP)\s+[A-Za-z0-9._~+/=-]{16,}/g, '$1 [REDACTED:credential]'],
|
|
237
257
|
[/\b([a-z][a-z0-9+.-]*:\/\/[^\s:/@]+:)[^\s@/]+@/gi, '$1[REDACTED:password]@'],
|
|
238
258
|
[
|
|
@@ -245,6 +265,32 @@ const SECRET_PATTERNS: ReadonlyArray<readonly [RegExp, string]> = [
|
|
|
245
265
|
],
|
|
246
266
|
];
|
|
247
267
|
|
|
268
|
+
const SECRET_PATTERNS: ReadonlyArray<readonly [RegExp, string]> = [
|
|
269
|
+
...SHAPED_SECRETS,
|
|
270
|
+
...CONTEXT_SECRETS,
|
|
271
|
+
];
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* The shaped credentials safe to lift from a message the person forgot to mark: each never starts
|
|
275
|
+
* inside a word (`task-runner-…` holds `sk-…`), and the two loosest shapes (`sk-…` and
|
|
276
|
+
* `<word>_live_<…>`, which long hyphenated or snake_case names also fit) must carry a digit. Fresh
|
|
277
|
+
* regex objects, so lifting never shares match state with `scrubSecrets`. Scrubbing a false match is
|
|
278
|
+
* harmless; lifting one would take words out of the person's message.
|
|
279
|
+
*/
|
|
280
|
+
export const SHAPED_SECRET_PATTERNS: readonly RegExp[] = SHAPED_SECRETS.map(([pattern]) => {
|
|
281
|
+
const loose =
|
|
282
|
+
pattern.source.startsWith('sk-[') || pattern.source.includes('(?:live|test|prod|runtime)');
|
|
283
|
+
const digit = loose ? '(?=[A-Za-z0-9_-]*\\d)' : '';
|
|
284
|
+
return new RegExp(`(?<![A-Za-z0-9_-])${digit}(?:${pattern.source})`, pattern.flags);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
/** How many credentials known only by context (`PASSWORD=…`, `Bearer …`, a URL password) the text holds. */
|
|
288
|
+
export function contextSecretHits(text: string): number {
|
|
289
|
+
let hits = 0;
|
|
290
|
+
for (const [pattern] of CONTEXT_SECRETS) hits += [...text.matchAll(pattern)].length;
|
|
291
|
+
return hits;
|
|
292
|
+
}
|
|
293
|
+
|
|
248
294
|
/** Replace credential-shaped substrings. Transcripts are exactly where leaked keys live. */
|
|
249
295
|
export function scrubSecrets(text: string): { text: string; hits: number } {
|
|
250
296
|
let hits = 0;
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @atlas
|
|
3
|
+
* @kind helper
|
|
4
|
+
* @partOf feature:evals
|
|
5
|
+
*/
|
|
1
6
|
/**
|
|
2
7
|
* The experiment gate (`.claude/skills/experiment/SKILL.md`), shared by Foundry's eval scripts and
|
|
3
8
|
* Oracle's replay runner: a wide run (more than TINY_MOMENTS moments, or whatever else the caller
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @atlas
|
|
3
|
+
* @kind service
|
|
4
|
+
* @partOf feature:wardens
|
|
5
|
+
*/
|
|
1
6
|
// The Warden pre-filter: one cheap local yes/no per Warden before it is asked. Data and pure
|
|
2
7
|
// functions only, so Oracle scores runs the same way; Foundry's provider makes the call. It fails
|
|
3
8
|
// open: a Warden is skipped only on a confident no, and anything else (an unsure score, an error, no
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @atlas
|
|
3
|
+
* @kind service
|
|
4
|
+
* @partOf feature:evals
|
|
5
|
+
*/
|
|
1
6
|
// Warden probes: the offline measurement of how well a Warden charter keeps the chorus quiet, in
|
|
2
7
|
// scope and right. Data and pure functions only, so Oracle can score runs on a core-only dependency;
|
|
3
8
|
// callers bring the probes, the Warden calls and the judge call.
|
package/src/harness.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @atlas
|
|
3
|
+
* @kind service
|
|
4
|
+
* @partOf primitive:agents
|
|
5
|
+
*/
|
|
1
6
|
import type { ExecutionResult } from './base-agent';
|
|
2
7
|
import { BoundedSet } from './bounded-set';
|
|
3
8
|
import type { ClarificationResult, ClarifyPayload } from './clarifier';
|
|
@@ -512,7 +517,7 @@ export class Harness {
|
|
|
512
517
|
const layerFilter = this.buildLayerFilter(undefined, undefined, requestedLayers);
|
|
513
518
|
const settled = await Promise.allSettled(
|
|
514
519
|
decisionStages.map(async (stage) => {
|
|
515
|
-
const span = trace.startDetached(`${stage.role}:${stage.agentId}`, stage.role
|
|
520
|
+
const span = trace.startDetached(`${stage.role}:${stage.agentId}`, stage.role, {
|
|
516
521
|
agentId: stage.agentId,
|
|
517
522
|
threadId: this.thread.id,
|
|
518
523
|
input: message.payload,
|
|
@@ -638,7 +643,7 @@ export class Harness {
|
|
|
638
643
|
}
|
|
639
644
|
|
|
640
645
|
// Trace and dispatch (foreground — blocks pipeline)
|
|
641
|
-
const stageSpan = trace.start(`${stage.role}:${agentId}`, stage.role
|
|
646
|
+
const stageSpan = trace.start(`${stage.role}:${agentId}`, stage.role, {
|
|
642
647
|
agentId,
|
|
643
648
|
threadId: this.thread.id,
|
|
644
649
|
input: message.payload,
|
package/src/hydrator.ts
CHANGED