@theoremjs/agents 0.3.4-next.464.g5fc5eed → 0.3.4-next.468.ge2da06f

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.
@@ -7,7 +7,7 @@ import { actionAt, DETECTORS, detects, } from './detectors.js';
7
7
  import { boundaryNote } from './egress.js';
8
8
  import { notePattern, SYSTEM_BOUNDARY } from './egress-patterns.js';
9
9
  import { imageLeakSpans, linkLeakSpans } from './egress-urls.js';
10
- import { CANARY_HIT, hitFromSpan, PROMPT_ECHO_HIT } from './hits.js';
10
+ import { CANARY_HIT, hitFromSpan, promptEchoHit } from './hits.js';
11
11
  import { injectionSpans } from './injection.js';
12
12
  import { promptEchoRanges } from './prompt-echo.js';
13
13
  import { DETECT_RULES, detectRule } from './rules.js';
@@ -122,13 +122,13 @@ function theoremSpans(detector, text, scope, skipImages = false) {
122
122
  }
123
123
  return sensitiveSpans(text, ONLY[detector]);
124
124
  }
125
- /** The hit for a match of `detector`. One of ours never carries what it matched. */
125
+ /** The hit for a match of `detector`. A canary leak never carries the token. */
126
126
  function hitOf(detector, text, span) {
127
127
  const { start, end } = span;
128
128
  if (detector === 'canary_leak')
129
129
  return { ...CANARY_HIT, span: { start, end } };
130
130
  if (detector === 'prompt_leak')
131
- return { ...PROMPT_ECHO_HIT, span: { start, end } };
131
+ return promptEchoHit(text, start, end);
132
132
  if (detector === 'tool_instructions' && span.signal) {
133
133
  const severity = span.signal === 'order' || span.signal === 'authority' ? 'medium' : 'high';
134
134
  return { ...hitFromSpan(text, span, DETECT_RULES[detector], severity), signal: span.signal };
@@ -8,7 +8,7 @@ declare const WITHHELD_REASON: {
8
8
  readonly providerToolLeak: "a provider-side tool already sent the canary or system prompt";
9
9
  readonly egress: "Turn withheld: egress disclosure violation";
10
10
  };
11
- /** The prompt-echo hit, when `text` repeats the guarded system prompt's own words. */
11
+ /** A hit for each stretch of `text` that repeats the guarded system prompt's own words. */
12
12
  declare function promptEchoHits(text: string, privateSystem?: readonly string[], canary?: string): GuardrailHit[];
13
13
  /** What a prompt-leak check reads of the turn. */
14
14
  type LeakScope = Pick<GuardrailContext, 'canary' | 'canaryGiven' | 'privateSystem'>;
@@ -1,7 +1,7 @@
1
1
  import { canaryNoteMarker, guardedEventTexts, scanTextForCanaryLeak } from './canary.js';
2
2
  import { SYSTEM_BOUNDARY } from './egress-patterns.js';
3
- import { CANARY_HIT, PROMPT_ECHO_HIT } from './hits.js';
4
- import { scanTextForPromptEcho } from './prompt-echo.js';
3
+ import { CANARY_HIT, promptEchoHit } from './hits.js';
4
+ import { promptEchoRanges } from './prompt-echo.js';
5
5
  import { DETECT_RULES, EGRESS_RULES } from './rules.js';
6
6
  /** Why a reply was withheld, for the builder (`errorInternal`); the user reads `error.safety`. */
7
7
  const WITHHELD_REASON = {
@@ -13,11 +13,11 @@ const WITHHELD_REASON = {
13
13
  function canaryHits(text, canary) {
14
14
  return canary && scanTextForCanaryLeak(text, canary) ? [CANARY_HIT] : [];
15
15
  }
16
- /** The prompt-echo hit, when `text` repeats the guarded system prompt's own words. */
16
+ /** A hit for each stretch of `text` that repeats the guarded system prompt's own words. */
17
17
  function promptEchoHits(text, privateSystem, canary) {
18
- return privateSystem && scanTextForPromptEcho(text, privateSystem, canary)
19
- ? [PROMPT_ECHO_HIT]
20
- : [];
18
+ if (!privateSystem)
19
+ return [];
20
+ return promptEchoRanges(text, privateSystem, canary).map(([start, end]) => promptEchoHit(text, start, end));
21
21
  }
22
22
  /** The system-prompt leak hits: the canary unless it was given, and the prompt's own words when guarded. */
23
23
  function promptLeakHits(text, scope) {
@@ -78,9 +78,12 @@ function eventLeak(event, scope, detect, boundary) {
78
78
  found.push([canaryAction, CANARY_HIT]);
79
79
  }
80
80
  const promptAction = detect.prompt_leak[boundary];
81
- if (promptAction !== 'ignore' &&
82
- texts.some((text) => promptEchoHits(text, scope.privateSystem, scope.canary).length > 0)) {
83
- found.push([promptAction, PROMPT_ECHO_HIT]);
81
+ if (promptAction !== 'ignore') {
82
+ for (const text of texts) {
83
+ for (const hit of promptEchoHits(text, scope.privateSystem, scope.canary)) {
84
+ found.push([promptAction, hit]);
85
+ }
86
+ }
84
87
  }
85
88
  if (found.length === 0)
86
89
  return undefined;
@@ -14,8 +14,11 @@ const OTHER_HINTS = {
14
14
  [EGRESS_RULES.unscannable]: 'egress.hint_unscannable',
15
15
  [EGRESS_RULES.providerToolLeak]: 'egress.hint_provider_tool_leak',
16
16
  };
17
- /** The rules whose match is the profile's own, and is never repeated. */
18
- const UNQUOTED = [DETECT_RULES.canary_leak, DETECT_RULES.prompt_leak];
17
+ /**
18
+ * The rules whose match is never repeated: the canary is the profile's own secret. A prompt leak is
19
+ * quoted, since the model already holds the instruction it repeated and must know which passage.
20
+ */
21
+ const UNQUOTED = [DETECT_RULES.canary_leak];
19
22
  /** How many matches a detector's line quotes, and how much of each. */
20
23
  const MAX_QUOTED = 3;
21
24
  const MAX_QUOTE = 120;
@@ -1,14 +1,14 @@
1
1
  import type { GuardrailEvent, GuardrailHit, Severity } from './types.js';
2
2
  /** A canary leak. Never carries the live token, only a placeholder. */
3
3
  declare const CANARY_HIT: GuardrailHit;
4
- /** A prompt leak. Never carries the words: they are the private system instruction's. */
5
- declare const PROMPT_ECHO_HIT: GuardrailHit;
6
4
  /** A guardrail hit for a span of the text, with the matched text sliced from it. */
7
5
  declare function hitFromSpan(text: string, span: {
8
6
  start: number;
9
7
  end: number;
10
8
  name?: string;
11
9
  }, rule: string, severity: Severity): GuardrailHit;
10
+ /** A prompt leak: the stretch of `text` from `start` to `end` that repeats the private system instruction. */
11
+ declare function promptEchoHit(text: string, start: number, end: number): GuardrailHit;
12
12
  /** `match` is the caught text itself, so hosts and traces get it only via `observability.include.guardrailMatchPreview`. */
13
13
  declare function projectGuardrailEvent(event: GuardrailEvent, includeMatch: boolean): GuardrailEvent;
14
- export { CANARY_HIT, hitFromSpan, PROMPT_ECHO_HIT, projectGuardrailEvent };
14
+ export { CANARY_HIT, hitFromSpan, projectGuardrailEvent, promptEchoHit };
@@ -5,8 +5,6 @@ const CANARY_HIT = {
5
5
  severity: 'high',
6
6
  match: '[canary]',
7
7
  };
8
- /** A prompt leak. Never carries the words: they are the private system instruction's. */
9
- const PROMPT_ECHO_HIT = { rule: DETECT_RULES.prompt_leak, severity: 'high' };
10
8
  /** A guardrail hit for a span of the text, with the matched text sliced from it. */
11
9
  function hitFromSpan(text, span, rule, severity) {
12
10
  return {
@@ -17,6 +15,10 @@ function hitFromSpan(text, span, rule, severity) {
17
15
  ...(span.name === undefined ? {} : { pattern: span.name }),
18
16
  };
19
17
  }
18
+ /** A prompt leak: the stretch of `text` from `start` to `end` that repeats the private system instruction. */
19
+ function promptEchoHit(text, start, end) {
20
+ return hitFromSpan(text, { start, end }, DETECT_RULES.prompt_leak, 'high');
21
+ }
20
22
  /** `match` is the caught text itself, so hosts and traces get it only via `observability.include.guardrailMatchPreview`. */
21
23
  function projectGuardrailEvent(event, includeMatch) {
22
24
  if (includeMatch) {
@@ -27,4 +29,4 @@ function projectGuardrailEvent(event, includeMatch) {
27
29
  hits: event.hits.map(({ match: _match, ...hit }) => hit),
28
30
  };
29
31
  }
30
- export { CANARY_HIT, hitFromSpan, PROMPT_ECHO_HIT, projectGuardrailEvent };
32
+ export { CANARY_HIT, hitFromSpan, projectGuardrailEvent, promptEchoHit };
@@ -15,7 +15,7 @@ import { boundaryNote } from './egress.js';
15
15
  import { notePattern, SYSTEM_BOUNDARY } from './egress-patterns.js';
16
16
  import { createEgressStream, loadEgressAutomata } from './egress-stream.js';
17
17
  import { imageLeakSpans, linkLeakSpans } from './egress-urls.js';
18
- import { CANARY_HIT, PROMPT_ECHO_HIT } from './hits.js';
18
+ import { CANARY_HIT } from './hits.js';
19
19
  import { labelText } from './labels.js';
20
20
  import { promptEchoHoldFrom, promptEchoRanges, promptEchoScanFrom } from './prompt-echo.js';
21
21
  import { DETECT_RULES } from './rules.js';
@@ -27,6 +27,7 @@ const OMIT_KEYS = {
27
27
  const IMAGE_HIT = { rule: DETECT_RULES.ungiven_images, severity: 'high' };
28
28
  const LINK_HIT = { rule: DETECT_RULES.ungiven_links, severity: 'high' };
29
29
  const MARKER_HIT = { rule: DETECT_RULES.marker_leak, severity: 'high' };
30
+ const PROMPT_ECHO_HIT = { rule: DETECT_RULES.prompt_leak, severity: 'high' };
30
31
  /** Where `raw`, an offset into `view.text`, stands in the thought: a placeholder's start, or with `end` its end. */
31
32
  function shownAt(view, raw, end = false) {
32
33
  let piece = view.pieces[0];
@@ -412,6 +412,8 @@ async function* executeSingleAttemptCycle(args) {
412
412
  // invariant: The attempt's reply spans its before_end re-entries; only a new attempt starts it again.
413
413
  state.replyText = '';
414
414
  state.replyMessages = [];
415
+ // invariant: The leak check's carry is the end of this attempt's reply; a retry's is read on its own.
416
+ state.canaryCarry = undefined;
415
417
  let latestStructured;
416
418
  for (;;) {
417
419
  state.attemptEvents = [];
@@ -348,7 +348,7 @@ export interface ProfileOutputsSpec {
348
348
  export interface PrivateSystemPart {
349
349
  private: string;
350
350
  }
351
- /** One part of a system prompt: plain text or a part kept out of traces. */
351
+ /** One part of a system prompt: shareable text, or private text a reply may not repeat. */
352
352
  export type SystemPart = string | PrivateSystemPart;
353
353
  /**
354
354
  * A system prompt: a string, or parts sent concatenated as written. A string,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theoremjs/agents",
3
- "version": "0.3.4-next.464.g5fc5eed",
3
+ "version": "0.3.4-next.468.ge2da06f",
4
4
  "description": "A flat TypeScript agent kernel for typed profiles, deterministic turn execution, registered tools, provider adapters, guardrails, and host-injected traces.",
5
5
  "keywords": [
6
6
  "agent",