@sema-agent/client-core 0.88.0 → 0.89.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.
@@ -17,7 +17,7 @@ export interface RunsResourceLike {
17
17
  export interface ControlClientLike {
18
18
  runs: RunsResourceLike;
19
19
  }
20
- export type ControlSafetyCode = 'not_running' | 'invalid_content' | 'queue_full' | 'duplicate_input_id' | 'ambiguous_target' | 'still_running' | 'blocked_by_hook' | 'steering_other' | 'cancel_lost_race' | 'cancel_run_not_running' | 'not_found';
20
+ export type ControlSafetyCode = 'not_running' | 'invalid_content' | 'queue_full' | 'duplicate_input_id' | 'ambiguous_target' | 'still_running' | 'blocked_by_hook' | 'ledger_unavailable' | 'steering_other' | 'cancel_lost_race' | 'cancel_run_not_running' | 'not_found';
21
21
  export declare class ControlSafetyError extends Error {
22
22
  readonly code: ControlSafetyCode;
23
23
  readonly cause?: unknown | undefined;
@@ -71,3 +71,9 @@ export interface ErrCodes {
71
71
  status?: number | undefined;
72
72
  }
73
73
  export declare function errCodes(e: unknown): ErrCodes;
74
+ export declare function steeringStatusIs4xx(e: object): boolean;
75
+ export interface SteeringRefusedBeforeEngine {
76
+ safety: ControlSafetyCode;
77
+ advice: string;
78
+ }
79
+ export declare function steeringRefusedBeforeEngine(e: object): SteeringRefusedBeforeEngine | undefined;
@@ -64,8 +64,9 @@ export class ControlRouter {
64
64
  catch (e) {
65
65
  const verdict = steeringVerdict(e);
66
66
  if (verdict !== undefined) {
67
+ const safety = steeringSafetyOf(e, verdict);
67
68
  const wireCode = errCodes(e).code ?? errCodes(e).name ?? 'steering.<unknown>';
68
- throw new ControlSafetyError(`${wireCode} — ${STEERING_ADVICE.get(verdict) ?? 'steering refused'}`, verdict, e);
69
+ throw new ControlSafetyError(`${wireCode} — ${steeringAdviceOf(e, safety) ?? 'steering refused'}`, safety, e);
69
70
  }
70
71
  throw e;
71
72
  }
@@ -138,12 +139,14 @@ const STEERING_CODE_TO_SAFETY = new Map([
138
139
  ['steering.ambiguous_label', 'ambiguous_target'],
139
140
  ['steering.still_running', 'still_running'],
140
141
  ['steering.blocked_by_hook', 'blocked_by_hook'],
142
+ ['steering.ledger_unavailable', 'ledger_unavailable'],
141
143
  ]);
142
144
  const STEERING_NAME_TO_SAFETY = new Map([
143
145
  ['SteeringNotRunningError', 'not_running'],
144
146
  ['SteeringInvalidContentError', 'invalid_content'],
145
147
  ['SteeringAmbiguousTargetError', 'ambiguous_target'],
146
148
  ['SteeringStillRunningError', 'still_running'],
149
+ ['SteeringLedgerUnavailableError', 'ledger_unavailable'],
147
150
  ['SteeringError', 'steering_other'],
148
151
  ]);
149
152
  const STEERING_ADVICE = new Map([
@@ -154,8 +157,47 @@ const STEERING_ADVICE = new Map([
154
157
  ['ambiguous_target', 'more than one live target matches — address it uniquely (sub-agent: parentToolCallId; workflow: a more specific label); retrying verbatim yields the same result'],
155
158
  ['still_running', 'the target is STILL RUNNING — steer it instead of resuming, or wait for it to settle (this is the exact opposite of not_running: never collapse the two)'],
156
159
  ['blocked_by_hook', "this deployment's userPromptSubmit gate refused the input (block, timeout and crash all report this one code, fail-closed) — the input was NOT accepted: no human_input frame, the inputId is not on the ledger, so editing the text and sending again is a normal retry, not a duplicate"],
160
+ ['ledger_unavailable', 'the server could not record this steer in its user-input ledger and refused it before anything reached the engine — the input was NOT delivered; resend the same text later (the same Idempotency-Key is fine)'],
157
161
  ['steering_other', 'an unrecognized steering.* refusal (open set) — the steer did NOT land; log the errorCode verbatim, do not guess its meaning'],
158
162
  ]);
163
+ const STEERING_OTHER_UNKNOWN_ADVICE = 'an unrecognized steering.* refusal (open set) — treat delivery as unknown (do not resend as if it had certainly been dropped); log the errorCode verbatim, do not guess its meaning';
164
+ export function steeringStatusIs4xx(e) {
165
+ let status;
166
+ try {
167
+ status = errCodes(e).status;
168
+ }
169
+ catch {
170
+ return false;
171
+ }
172
+ return status !== undefined && status >= 400 && status < 500;
173
+ }
174
+ function steeringAdviceOf(e, safety) {
175
+ if (safety === 'steering_other' && !(typeof e === 'object' && e !== null && steeringStatusIs4xx(e)))
176
+ return STEERING_OTHER_UNKNOWN_ADVICE;
177
+ return STEERING_ADVICE.get(safety);
178
+ }
179
+ const STEERING_REFUSED_BEFORE_ENGINE = new Map([['ledger_unavailable', 503]]);
180
+ export function steeringRefusedBeforeEngine(e) {
181
+ try {
182
+ const { status } = errCodes(e);
183
+ if (status === undefined)
184
+ return undefined;
185
+ const safety = steeringVerdict(e);
186
+ if (safety === undefined || STEERING_REFUSED_BEFORE_ENGINE.get(safety) !== status)
187
+ return undefined;
188
+ const advice = STEERING_ADVICE.get(safety);
189
+ return advice === undefined ? undefined : { safety, advice };
190
+ }
191
+ catch {
192
+ return undefined;
193
+ }
194
+ }
195
+ function steeringSafetyOf(e, verdict) {
196
+ if (!STEERING_REFUSED_BEFORE_ENGINE.has(verdict))
197
+ return verdict;
198
+ const refused = typeof e === 'object' && e !== null ? steeringRefusedBeforeEngine(e) : undefined;
199
+ return refused !== undefined && refused.safety === verdict ? verdict : 'steering_other';
200
+ }
159
201
  function steeringVerdict(e) {
160
202
  const { code, name } = errCodes(e);
161
203
  if (typeof code === 'string' && code.startsWith('steering.')) {
@@ -0,0 +1,17 @@
1
+ export declare function refuseBadDisableSkills(v: unknown, who: string): void;
2
+ export declare function refuseDisableSkillsConflict(disableSkills: unknown, skills: unknown, agents: unknown, who: string): void;
3
+ type DisableSkillsWithheldCause = 'not_reported' | 'unsupported' | 'unprobed' | 'unreadable';
4
+ export type DisableSkillsDeclaration = {
5
+ readonly field: {
6
+ readonly disableSkills: true;
7
+ };
8
+ readonly withheld?: undefined;
9
+ } | {
10
+ readonly field: Readonly<Record<string, never>>;
11
+ readonly withheld?: {
12
+ readonly cause: DisableSkillsWithheldCause;
13
+ readonly text: string;
14
+ };
15
+ };
16
+ export declare function disableSkillsDeclaration(want: unknown, baseUrl: string | undefined): DisableSkillsDeclaration;
17
+ export {};
@@ -0,0 +1,52 @@
1
+ import { engineCapState } from './engineCapsCache.js';
2
+ const KEY = 'disableSkills';
3
+ const shapeOf = (v) => {
4
+ try {
5
+ return v === null ? 'null' : Array.isArray(v) ? 'typeof=object / array' : typeof v === 'string' ? `typeof=string(长 ${v.length})` : `typeof=${typeof v}`;
6
+ }
7
+ catch {
8
+ return `typeof=${typeof v} / unreadable`;
9
+ }
10
+ };
11
+ export function refuseBadDisableSkills(v, who) {
12
+ if (v === undefined || v === null || v === true || v === false)
13
+ return;
14
+ throw new TypeError(`${who}: disableSkills 只接受布尔或缺席(undefined/null)—— 收到 ${shapeOf(v)}。` +
15
+ '这一位是「这一发一只技能都不装」的声明:本层若把坏值折成缺席,这一次运行会照常装上技能而声明人不会知道;' +
16
+ '上游对非布尔值同样拒收。错误文本只报形状,不回显值。');
17
+ }
18
+ const nonEmptyArray = (v) => Array.isArray(v) && v.length > 0;
19
+ const anyAgentCarriesSkills = (agents) => Array.isArray(agents) && agents.some((a) => typeof a === 'object' && a !== null && nonEmptyArray(a.skills));
20
+ export function refuseDisableSkillsConflict(disableSkills, skills, agents, who) {
21
+ if (disableSkills !== true)
22
+ return;
23
+ const inSkills = nonEmptyArray(skills);
24
+ const inAgents = anyAgentCarriesSkills(agents);
25
+ if (!inSkills && !inAgents)
26
+ return;
27
+ const which = inSkills && inAgents ? 'skills 与 agents[].skills' : inSkills ? 'skills' : 'agents[].skills';
28
+ throw new TypeError(`${who}: disableSkills: true(这一发一只技能都不装)与非空的 ${which} 同在 —— 二选一:要关技能就别交技能,要交技能就别带 disableSkills。` +
29
+ '上游对这一组合同样拒收(在任何东西跑起来之前);本层在装配前拒,不让它走一次往返。错误文本只报键名,不回显技能名。');
30
+ }
31
+ const WITHHELD_TEXT = Object.freeze({
32
+ not_reported: 'Skills were not turned off for this request: the connected engine does not report that it can turn them off, and an engine that does not know this setting rejects the whole request, so the setting was left out and this run keeps its usual skills.',
33
+ unsupported: 'Skills were not turned off for this request: the connected engine reports that it cannot turn them off, so the setting was left out and this run keeps its usual skills.',
34
+ unprobed: 'Skills were not turned off for this request: what the connected engine supports has not been read yet, and an engine that does not know this setting rejects the whole request, so the setting was held back and this run keeps its usual skills.',
35
+ unreadable: 'Skills were not turned off for this request: the connected engine reported whether it can turn them off in a form this version cannot read, so the setting was left out and this run keeps its usual skills.',
36
+ });
37
+ const WITHHELD_CAUSE_OF = {
38
+ absent: 'not_reported',
39
+ false: 'unsupported',
40
+ unprobed: 'unprobed',
41
+ non_boolean: 'unreadable',
42
+ };
43
+ export function disableSkillsDeclaration(want, baseUrl) {
44
+ refuseBadDisableSkills(want, 'disableSkillsDeclaration');
45
+ if (want !== true)
46
+ return Object.freeze({ field: Object.freeze({}) });
47
+ const state = engineCapState(baseUrl, KEY);
48
+ if (state === 'true')
49
+ return Object.freeze({ field: Object.freeze({ disableSkills: true }) });
50
+ const cause = WITHHELD_CAUSE_OF[state];
51
+ return Object.freeze({ field: Object.freeze({}), withheld: Object.freeze({ cause, text: WITHHELD_TEXT[cause] }) });
52
+ }
@@ -66,7 +66,7 @@ const TEXT_BY_KIND = {
66
66
  };
67
67
  const isStructuralKind = (kind) => kind !== 'secret';
68
68
  const MARKER_WITH_DOTS = /^[. ]*(?:(?:\u00abredacted(?::[a-z-]{1,16}){0,2}\u00bb|\*{3})[. ]*)+$/;
69
- function valueIsWholeMarker(S, start, end) {
69
+ function valueIsWholeMarker(S, start, end, tokenInner = true) {
70
70
  const text = S.text;
71
71
  let k = start;
72
72
  while (k < end && (S.dropped(k) || text[k] === '.' || text[k] === ' '))
@@ -92,11 +92,11 @@ function valueIsWholeMarker(S, start, end) {
92
92
  if (retired) {
93
93
  while (j < end && (S.dropped(j) || S.painted(j) || text[j] === '.' || text[j] === ' '))
94
94
  j++;
95
- return j >= end || credentialWordStartsExactlyAt(text, j) || credentialPairAt(S, j);
95
+ return j >= end || credentialWordStartsExactlyAt(text, j, tokenInner) || credentialPairAt(S, j);
96
96
  }
97
97
  while (j < end && (S.dropped(j) || S.painted(j)))
98
98
  j++;
99
- return j >= end || !isKeyWordChar(text.charCodeAt(j)) || credentialWordStartsExactlyAt(text, j) || credentialPairAt(S, j);
99
+ return j >= end || !isKeyWordChar(text.charCodeAt(j)) || credentialWordStartsExactlyAt(text, j, tokenInner) || credentialPairAt(S, j);
100
100
  }
101
101
  const CREDENTIAL_NAME = /(?:(?:access|refresh|id|client|api|x[-_]api|session|auth)[-_]?)?(?:token|key|secret|password|authorization|credential)s?|bearer|basic/iy;
102
102
  const LABEL_SEPARATOR = /(?:\\*["'])?[\s\u115F\u1160\u3164\uFFA0\u2800]*[:=\uFF1A\uFF1D]/y;
@@ -379,7 +379,7 @@ function urlCandidates(text, out, regions, unitPos = () => false, soft) {
379
379
  let p = end;
380
380
  while (p < text.length && (text[p] === VIEW_SEPARATOR || isUnicodeBlank(text.charCodeAt(p))))
381
381
  p++;
382
- if (credentialWordStartsNear(text, p))
382
+ if (nextPairBeyondFragmentAt(text, p))
383
383
  break;
384
384
  end = p;
385
385
  continue;
@@ -759,7 +759,8 @@ const NAME_BLANK_RANGES = [
759
759
  const isNameBlank = (cp) => NAME_BLANK_RANGES.some(([a, b]) => cp >= a && cp <= b);
760
760
  const isFoldedBlank = (c) => isNameBlank(c) && isRegexSpace(c);
761
761
  const isUnicodeBlank = (c) => (c > 0x7f && isRegexSpace(c) && c !== 0xfeff) || isNameBlank(c);
762
- const SECRET_SCHEME_WORD = /(?:\b|(?<=\\[A-Za-z"']))(bearer|basic)[\s:=\uFF1A\uFF1D\u115F\u1160\u3164\uFFA0\u2800]+/gi;
762
+ const SECRET_SCHEME_WORD = /(?:\b|(?<=\\[A-Za-z"']))(bearer|basic|token)[\s:=\uFF1A\uFF1D\u115F\u1160\u3164\uFFA0\u2800]+/gi;
763
+ const schemeWordIsInner = (word, tokenInner) => tokenInner || word.toLowerCase() !== 'token';
763
764
  const SECRET_LABELLED_WORD = /(?:\b|(?<=_)|(?<=\\[A-Za-z"']))((?:access[-_ ]?|refresh[-_ ]?|id[-_ ]?|client[-_ ]?|api[-_ ]?|x[-_]api[-_ ]?|session[-_ ]?|auth[-_ ]?)?(?:token|key|secret|password|authorization|credential)s?)(?:\\*["'])?[\s\u115F\u1160\u3164\uFFA0\u2800]*[:=\uFF1A\uFF1D][\s\u115F\u1160\u3164\uFFA0\u2800]*/gi;
764
765
  const DIAGNOSTIC_VALUE_WORDS = new Set([
765
766
  'missing', 'required', 'invalid', 'expired', 'unauthorized', 'unauthenticated', 'authentication',
@@ -777,6 +778,37 @@ const SCHEME_FOLLOWER_WORDS = new Set([
777
778
  'setup', 'plan', 'tier', 'info', 'information', 'support', 'level', 'string', 'config', 'configuration', 'settings',
778
779
  'version', 'check', 'validation', 'error', 'errors', 'failed', 'failure', 'credentials', 'user', 'username',
779
780
  ]);
781
+ const TOKEN_FOLLOWER_WORDS = new Set([
782
+ ...SCHEME_FOLLOWER_WORDS,
783
+ 'count', 'counts', 'counting', 'counter', 'counters', 'limit', 'limits', 'budget', 'budgets', 'window', 'windows', 'estimate', 'estimates',
784
+ 'estimation', 'estimator', 'length', 'size', 'cost', 'costs', 'price', 'pricing', 'rate', 'rates', 'quota', 'quotas', 'cap', 'caps', 'ceiling',
785
+ 'threshold', 'total', 'totals', 'accounting', 'allocation', 'consumption', 'overflow', 'savings', 'reduction', 'spend', 'metrics', 'stats',
786
+ 'boundary', 'boundaries', 'stream', 'streaming', 'position', 'positions', 'index', 'offset', 'probability', 'probabilities', 'logprob',
787
+ 'logprobs', 'sequence', 'encoding', 'tokenizer', 'vocabulary', 'id', 'ids', 'maximum', 'minimum', 'max', 'min', 'ratio', 'table', 'line',
788
+ 'lines', 'unit', 'units', 'indices',
789
+ 'refresh', 'refreshed', 'refreshes', 'refreshing', 'exchange', 'exchanged', 'endpoint', 'endpoints', 'request', 'requests', 'response',
790
+ 'responses', 'store', 'stored', 'storage', 'cache', 'cached', 'caching', 'file', 'files', 'path', 'dir', 'directory', 'source', 'provider',
791
+ 'providers', 'supplier', 'helper', 'command', 'env', 'environment', 'variable', 'lifetime', 'ttl', 'expiry', 'expiration', 'expires', 'expiring',
792
+ 'rotation', 'rotated', 'revocation', 'revoke', 'introspection', 'verification', 'verified', 'generation', 'generated', 'issuer', 'issued',
793
+ 'audience', 'claims', 'scope', 'scopes', 'permission', 'permissions', 'owner', 'name', 'label', 'hash', 'signature', 'payload', 'body',
794
+ 'cookie', 'kind', 'types', 'data', 'metadata', 'envelope', 'binding', 'bound', 'renewal', 'minted', 'acquired', 'fetch', 'fetched', 'parsing',
795
+ 'parse', 'parsed', 'persisted', 'url', 'urls', 'client', 'argument', 'flag', 'option', 'field', 'governance', 'target',
796
+ 'has', 'have', 'had', 'will', 'would', 'could', 'may', 'might', 'does', 'did', 'do', 'doesn', 'isn', 'wasn', 'hasn', 'didn', 'won', 'couldn',
797
+ 'wouldn', 'shouldn', 'aren', 'weren', 'haven', 'been', 'being', 'needs', 'need', 'appears', 'seems', 'looks', 'lives', 'stays', 'remains',
798
+ 'starts', 'stopped', 'closed', 'arrived', 'surfaced', 'vanished', 'belongs', 'carries', 'contains', 'lacks', 'matches', 'match', 'changed',
799
+ 'created', 'deleted', 'removed', 'updated', 'exceeded', 'exceeds', 'exhausted', 'reached', 'returned', 'returns', 'received', 'provided',
800
+ 'supplied', 'found', 'used', 'using', 'saved', 'loaded', 'accepted', 'validated', 'passed', 'sent', 'fails', 'works', 'already', 'still',
801
+ 'also', 'only', 'never', 'now', 'here', 'there', 'then', 'than', 'so', 'but', 'as', 'if', 'when', 'while', 'until', 'before', 'after',
802
+ 'since', 'because', 'via', 'into', 'over', 'under', 'within', 'without', 'per', 'each', 'every', 'any', 'all', 'its', 'which', 'whose',
803
+ 'please', 'run', 'try', 'retry', 'see', 'use', 'visit', 'reauthenticate', 'reconnect',
804
+ ]);
805
+ function schemeFollowerReleased(scheme, value, afterPathSlash) {
806
+ const word = value.replace(/[.,;:!?)]+$/, '').toLowerCase();
807
+ const isToken = scheme.toLowerCase() === 'token';
808
+ if (isToken && afterPathSlash && /^\d{3}$/.test(word))
809
+ return true;
810
+ return (isToken ? TOKEN_FOLLOWER_WORDS : SCHEME_FOLLOWER_WORDS).has(word);
811
+ }
780
812
  const AUTH_SCHEME_WORDS = new Set([
781
813
  'bearer', 'basic', 'digest', 'token', 'bot', 'ssws', 'api-key', 'apikey', 'key', 'hmac', 'oauth', 'oauth2', 'jwt',
782
814
  'negotiate', 'ntlm', 'hawk', 'mac', 'dsn', 'splunk', 'sapisidhash', 'googlelogin', 'aws', 'client-id', 'pat',
@@ -786,9 +818,11 @@ const BARE_VALUE_STOP_CHARS = new Set([',', ';', '"', "'", '\\', '(', ')', '[',
786
818
  const isBareValueStop = (ch) => ch <= ' ' || BARE_VALUE_STOP_CHARS.has(ch);
787
819
  class InnerWordIndex {
788
820
  text;
821
+ tokenInner;
789
822
  probes;
790
- constructor(text) {
823
+ constructor(text, tokenInner = true) {
791
824
  this.text = text;
825
+ this.tokenInner = tokenInner;
792
826
  this.probes = [
793
827
  { re: new RegExp(SECRET_LABELLED_WORD.source, SECRET_LABELLED_WORD.flags), scheme: false, from: -1, hit: null },
794
828
  { re: new RegExp(SECRET_SCHEME_WORD.source, SECRET_SCHEME_WORD.flags), scheme: true, from: -1, hit: null },
@@ -800,7 +834,11 @@ class InnerWordIndex {
800
834
  const reusable = p.from !== -1 && q >= p.from && (p.hit === null || q <= p.hit.start);
801
835
  if (!reusable) {
802
836
  p.re.lastIndex = q;
803
- const m = p.re.exec(this.text);
837
+ let m = p.re.exec(this.text);
838
+ while (m !== null && p.scheme && !schemeWordIsInner(m[1] ?? '', this.tokenInner)) {
839
+ p.re.lastIndex = m.index + 1;
840
+ m = p.re.exec(this.text);
841
+ }
804
842
  p.from = q;
805
843
  if (m === null)
806
844
  p.hit = null;
@@ -885,7 +923,7 @@ function scanSecretValue(text, from, idx) {
885
923
  let p = j;
886
924
  while (p < text.length && text[p] === VIEW_SEPARATOR)
887
925
  p++;
888
- if (credentialWordStartsNear(text, p))
926
+ if (credentialWordStartsNear(text, p, idx?.tokenInner ?? true))
889
927
  break;
890
928
  j = p;
891
929
  continue;
@@ -935,6 +973,22 @@ function isTokenCountLabel(text, at, label) {
935
973
  const lastSep = Math.max(head.lastIndexOf('_'), head.lastIndexOf('-'));
936
974
  return TOKEN_COUNT_HEAD_WORDS.has(head.slice(lastSep + 1));
937
975
  }
976
+ function isParserDiagnosticToken(text, at, label) {
977
+ if (label.toLowerCase() !== 'token' || at < 1)
978
+ return false;
979
+ if (text.charAt(at - 1) !== ' ')
980
+ return false;
981
+ const head = 'unexpected';
982
+ const start = at - 1 - head.length;
983
+ if (start < 0 || text.slice(start, at - 1).toLowerCase() !== head)
984
+ return false;
985
+ if (start === 0)
986
+ return true;
987
+ const before = text.charAt(start - 1);
988
+ if (!/[A-Za-z0-9_]/.test(before))
989
+ return true;
990
+ return start >= 2 && text.charAt(start - 2) === '\\' && /[A-Za-z"']/.test(before);
991
+ }
938
992
  const BACKTICKED_CREDENTIAL_NAME = /^\x60[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+\x60[.,;:!?)]*$/;
939
993
  const CREDENTIAL_NAME_SEGMENTS = new Set([
940
994
  'KEY', 'KEYS', 'TOKEN', 'TOKENS', 'SECRET', 'SECRETS', 'PASSWORD', 'PASSWD', 'PWD', 'CREDENTIAL', 'CREDENTIALS',
@@ -961,29 +1015,41 @@ function labelStartsAt(text, at) {
961
1015
  re.lastIndex = at;
962
1016
  return re.test(text);
963
1017
  }
964
- function credentialWordStartsAt(text, at) {
1018
+ function credentialWordStartsAt(text, at, tokenInner = true) {
965
1019
  if (at >= text.length)
966
1020
  return false;
967
- if (credentialWordStartsExactlyAt(text, at))
1021
+ if (credentialWordStartsExactlyAt(text, at, tokenInner))
968
1022
  return true;
969
1023
  let k = at;
970
1024
  while (k < text.length && text.charCodeAt(k) === 0x5c)
971
1025
  k++;
972
1026
  if (text[k] === '"' || text[k] === "'")
973
1027
  k++;
974
- return k > at && credentialWordStartsExactlyAt(text, k);
1028
+ return k > at && credentialWordStartsExactlyAt(text, k, tokenInner);
975
1029
  }
976
- function credentialWordStartsExactlyAt(text, at) {
1030
+ function credentialWordStartsExactlyAt(text, at, tokenInner = true) {
977
1031
  if (at >= text.length)
978
1032
  return false;
979
1033
  if (labelStartsAt(text, at))
980
1034
  return true;
981
1035
  const re = new RegExp(SECRET_SCHEME_WORD.source, 'iy');
982
1036
  re.lastIndex = at;
983
- return re.test(text);
984
- }
985
- function credentialWordStartsNear(text, at) {
986
- return credentialWordStartsAt(text, at) || credentialWordStartsAt(text, at + 1);
1037
+ const m = re.exec(text);
1038
+ return m !== null && schemeWordIsInner(m[1] ?? '', tokenInner);
1039
+ }
1040
+ function credentialWordStartsNear(text, at, tokenInner = true) {
1041
+ return credentialWordStartsAt(text, at, tokenInner) || credentialWordStartsAt(text, at + 1, tokenInner);
1042
+ }
1043
+ function innerWordMatchAt(text, at, tokenInner) {
1044
+ const lab = new RegExp(SECRET_LABELLED_WORD.source, 'iy');
1045
+ lab.lastIndex = at;
1046
+ const m = lab.exec(text);
1047
+ if (m !== null)
1048
+ return m;
1049
+ const sch = new RegExp(SECRET_SCHEME_WORD.source, 'iy');
1050
+ sch.lastIndex = at;
1051
+ const s = sch.exec(text);
1052
+ return s !== null && schemeWordIsInner(s[1] ?? '', tokenInner) ? s : null;
987
1053
  }
988
1054
  function nextPairBeyondFragmentAt(text, at) {
989
1055
  for (const p of [at, at + 1]) {
@@ -1195,13 +1261,15 @@ function addressExtentEnd(text, from) {
1195
1261
  }
1196
1262
  return end;
1197
1263
  }
1198
- function trailingLabelStart(text, from, end) {
1264
+ function trailingLabelStart(text, from, end, tokenInner = true) {
1199
1265
  const window = text.slice(from, Math.min(text.length, end + 12));
1200
1266
  const segLen = end - from;
1201
1267
  let best = -1;
1202
1268
  for (const src of [SECRET_SCHEME_WORD, SECRET_LABELLED_WORD]) {
1203
1269
  const re = new RegExp(src.source, src.flags);
1204
1270
  for (let hit = re.exec(window); hit !== null && hit.index < segLen; hit = re.exec(window)) {
1271
+ if (src === SECRET_SCHEME_WORD && !schemeWordIsInner(hit[1] ?? '', tokenInner))
1272
+ continue;
1205
1273
  if (hit.index + hit[0].length >= segLen && (best === -1 || hit.index < best))
1206
1274
  best = hit.index;
1207
1275
  }
@@ -1257,14 +1325,8 @@ function isTokenCountAt(text, at) {
1257
1325
  return m !== null && isTokenCountLabel(text, at, m[1] ?? '');
1258
1326
  }
1259
1327
  function innerWordHasValue(text, at, idx) {
1260
- for (const src of [SECRET_LABELLED_WORD, SECRET_SCHEME_WORD]) {
1261
- const re = new RegExp(src.source, 'iy');
1262
- re.lastIndex = at;
1263
- const m = re.exec(text);
1264
- if (m !== null)
1265
- return scanSecretValue(text, at + m[0].length, idx) !== null;
1266
- }
1267
- return false;
1328
+ const m = innerWordMatchAt(text, at, idx.tokenInner);
1329
+ return m !== null && scanSecretValue(text, at + m[0].length, idx) !== null;
1268
1330
  }
1269
1331
  function quoteSwallowedBefore(S, at) {
1270
1332
  let k = at - 1;
@@ -1411,11 +1473,13 @@ function quotedAddressEnd(S, from, to) {
1411
1473
  }
1412
1474
  return to;
1413
1475
  }
1414
- function labelCandidates(S, re, regions, out) {
1476
+ function labelCandidates(S, pass, regions, out) {
1415
1477
  const text = S.text;
1416
1478
  const unitAt = (k) => S.unitAt(k);
1417
- const schemePass = re === SECRET_SCHEME_WORD;
1418
- const idx = new InnerWordIndex(text);
1479
+ const re = pass === 'label' ? SECRET_LABELLED_WORD : SECRET_SCHEME_WORD;
1480
+ const schemePass = pass !== 'label';
1481
+ const tokenPass = pass === 'token';
1482
+ const idx = new InnerWordIndex(text, tokenPass);
1419
1483
  const extensionCache = new Map();
1420
1484
  let stops;
1421
1485
  const push = (start, end, kindText, te = -1, cls) => {
@@ -1482,7 +1546,7 @@ function labelCandidates(S, re, regions, out) {
1482
1546
  cut--;
1483
1547
  if (cut > p) {
1484
1548
  const rest = withoutSeparators(text.slice(p, cut));
1485
- if (rest.trim() !== '' && !valueIsWholeMarker(S, p, cut) && !isDiagnosticValue(rest))
1549
+ if (rest.trim() !== '' && !valueIsWholeMarker(S, p, cut, idx.tokenInner) && !isDiagnosticValue(rest))
1486
1550
  push(p, cut, REDACTED_SECRET);
1487
1551
  }
1488
1552
  if (markerHit && marker !== null) {
@@ -1492,14 +1556,7 @@ function labelCandidates(S, re, regions, out) {
1492
1556
  }
1493
1557
  if (!hasInner || inner === null)
1494
1558
  return;
1495
- const lab = new RegExp(SECRET_LABELLED_WORD.source, 'iy');
1496
- lab.lastIndex = inner.start;
1497
- let m = lab.exec(text);
1498
- if (m === null) {
1499
- const sch = new RegExp(SECRET_SCHEME_WORD.source, 'iy');
1500
- sch.lastIndex = inner.start;
1501
- m = sch.exec(text);
1502
- }
1559
+ const m = innerWordMatchAt(text, inner.start, idx.tokenInner);
1503
1560
  if (m === null)
1504
1561
  return;
1505
1562
  const after = inner.start + m[0].length;
@@ -1533,14 +1590,7 @@ function labelCandidates(S, re, regions, out) {
1533
1590
  const close = text.indexOf(q, from);
1534
1591
  outerEnd = close === -1 ? text.length : close;
1535
1592
  }
1536
- const lab = new RegExp(SECRET_LABELLED_WORD.source, 'iy');
1537
- lab.lastIndex = at;
1538
- let m = lab.exec(text);
1539
- if (m === null) {
1540
- const sch = new RegExp(SECRET_SCHEME_WORD.source, 'iy');
1541
- sch.lastIndex = at;
1542
- m = sch.exec(text);
1543
- }
1593
+ const m = innerWordMatchAt(text, at, idx.tokenInner);
1544
1594
  if (m === null)
1545
1595
  return;
1546
1596
  const inner = { start: at };
@@ -1583,10 +1633,16 @@ function labelCandidates(S, re, regions, out) {
1583
1633
  re.lastIndex = 0;
1584
1634
  for (let hit = re.exec(text); hit !== null; hit = re.exec(text)) {
1585
1635
  const label = hit[1] ?? '';
1636
+ if (schemePass && (label.toLowerCase() === 'token') !== tokenPass)
1637
+ continue;
1586
1638
  if (isTokenCountLabel(text, hit.index, label))
1587
1639
  continue;
1640
+ if (schemePass && isParserDiagnosticToken(text, hit.index, label))
1641
+ continue;
1642
+ if (schemePass && labelStartsAt(text, hit.index))
1643
+ continue;
1588
1644
  const from = hit.index + hit[0].length;
1589
- if (credentialWordStartsAt(text, from)) {
1645
+ if (credentialWordStartsAt(text, from, idx.tokenInner)) {
1590
1646
  pushInnerRemainder(from);
1591
1647
  re.lastIndex = from;
1592
1648
  continue;
@@ -1640,7 +1696,7 @@ function labelCandidates(S, re, regions, out) {
1640
1696
  continue;
1641
1697
  }
1642
1698
  }
1643
- if (valueIsWholeMarker(S, v.start, v.end)) {
1699
+ if (valueIsWholeMarker(S, v.start, v.end, idx.tokenInner)) {
1644
1700
  re.lastIndex = quoted ? from : v.end;
1645
1701
  continue;
1646
1702
  }
@@ -1672,7 +1728,7 @@ function labelCandidates(S, re, regions, out) {
1672
1728
  v.end = a;
1673
1729
  }
1674
1730
  if (!quoted) {
1675
- const trailing = trailingLabelStart(text, v.start, v.end);
1731
+ const trailing = trailingLabelStart(text, v.start, v.end, idx.tokenInner);
1676
1732
  if (trailing > 0 && innerWordHasValue(text, v.start + trailing, idx)) {
1677
1733
  let e = v.start + trailing;
1678
1734
  while (e > v.start && (isCutDelimiter(text.charAt(e - 1)) || text[e - 1] === VIEW_SEPARATOR))
@@ -1691,7 +1747,7 @@ function labelCandidates(S, re, regions, out) {
1691
1747
  re.lastIndex = quoted ? from : v.end;
1692
1748
  continue;
1693
1749
  }
1694
- if (schemePass && !quoted && SCHEME_FOLLOWER_WORDS.has(value.replace(/[.,;:!?)]+$/, '').toLowerCase())) {
1750
+ if (schemePass && !quoted && schemeFollowerReleased(label, value, text.charCodeAt(hit.index - 1) === 0x2f)) {
1695
1751
  re.lastIndex = v.end;
1696
1752
  continue;
1697
1753
  }
@@ -1730,7 +1786,7 @@ function labelCandidates(S, re, regions, out) {
1730
1786
  if (isAddressShaped(value, rawValue) || rawAddress) {
1731
1787
  const extentFrom = forbiddenAddress ? forbiddenAt : v.start;
1732
1788
  let end = quoted ? quotedAddressEnd(S, extentFrom, v.end) : addressExtentEnd(text, extentFrom);
1733
- const trailing = trailingLabelStart(text, v.start, end);
1789
+ const trailing = trailingLabelStart(text, v.start, end, idx.tokenInner);
1734
1790
  if (trailing !== -1)
1735
1791
  end = v.start + trailing;
1736
1792
  if (gluedToSingleUnit(v.start, hit.index, unitAt) || userinfoHostHidden(text, v.start, unitAt)) {
@@ -2357,7 +2413,7 @@ function joinedSurface(raw, V, spacesKept = false, M = null) {
2357
2413
  function screenSurfaces(text, o) {
2358
2414
  const face = o.face;
2359
2415
  if (face === null)
2360
- return { surfaces: [], rawSoft: null, rawPlain: null };
2416
+ return { surfaces: [], rawSoft: null, rawPlain: null, contents: [] };
2361
2417
  const unsafe = face.unsafe === null ? null : new RegExp(face.unsafe.source, 'u');
2362
2418
  const keepBlanks = face.keepBlanks === true;
2363
2419
  const chars = [];
@@ -2431,7 +2487,7 @@ function screenSurfaces(text, o) {
2431
2487
  i += w;
2432
2488
  }
2433
2489
  if (!changed)
2434
- return { surfaces: [], rawSoft: null, rawPlain: null };
2490
+ return { surfaces: [], rawSoft: null, rawPlain: null, contents: [] };
2435
2491
  const screen = chars.join('');
2436
2492
  const M = { ps: Int32Array.from(ps), pe: Int32Array.from(pe), pdot: Uint8Array.from(pdot), rawDot };
2437
2493
  const V = buildScanView(screen, o);
@@ -2451,7 +2507,8 @@ function screenSurfaces(text, o) {
2451
2507
  const joinedSpaced = o.escapeSpaces !== null ? joinedSurface(text, V, true, M) : null;
2452
2508
  if (joinedSpaced !== null)
2453
2509
  out.push(joinedSpaced);
2454
- return { surfaces: out, rawSoft, rawPlain };
2510
+ const contents = V.contents.map(([a, b]) => ({ sub: screen.slice(a, b), rawAt: (k) => M.ps[a + k] ?? 0, rawEndAt: (k) => M.pe[a + k] ?? 0 }));
2511
+ return { surfaces: out, rawSoft, rawPlain, contents };
2455
2512
  }
2456
2513
  function finalIsStructural(text, at) {
2457
2514
  if (text.charCodeAt(at) !== 0x5c)
@@ -2735,8 +2792,9 @@ function collectCandidates(text, o, nested) {
2735
2792
  if (o.words) {
2736
2793
  editsToCandidates(pemEdits(S, (blk) => { if (S === regionSurface)
2737
2794
  pemBlocksSeen.push(pemBlockToRaw(S, blk)); }), 'pem', cands);
2738
- labelCandidates(S, SECRET_SCHEME_WORD, regions, cands);
2739
- labelCandidates(S, SECRET_LABELLED_WORD, regions, cands);
2795
+ labelCandidates(S, 'scheme', regions, cands);
2796
+ labelCandidates(S, 'token', regions, cands);
2797
+ labelCandidates(S, 'label', regions, cands);
2740
2798
  editsToCandidates(authParamEdits(S.text), 'param', cands);
2741
2799
  editsToCandidates(proseLiteralEdits(S.text), 'prose', cands);
2742
2800
  }
@@ -2754,6 +2812,15 @@ function collectCandidates(text, o, nested) {
2754
2812
  raw.push({ start: a + iv.start, end: a + iv.end, kind: iv.kind, cls: 'nested', tokenEnd: a + iv.end, pretrimmed: true });
2755
2813
  }
2756
2814
  }
2815
+ for (const c of screen.contents) {
2816
+ const inner = collectCandidates(c.sub, o, true);
2817
+ for (const iv of resolveCandidates(c.sub, inner.cands, inner.rawUnit, inner.rawDot, o.words ? pemRegions(inner.pemBlocksSeen) : [], inner.rawSoft, inner.rawPlain)) {
2818
+ const start = c.rawAt(iv.start);
2819
+ const end = c.rawEndAt(iv.end - 1);
2820
+ if (end > start)
2821
+ raw.push({ start, end, kind: iv.kind, cls: 'nested', tokenEnd: end, pretrimmed: true });
2822
+ }
2823
+ }
2757
2824
  }
2758
2825
  return { cands: raw, rawUnit: V.rawUnit, rawDot: screen.surfaces[0]?.rawDot ?? null, rawSoft: screen.rawSoft, rawPlain: screen.rawPlain, pemBlocksSeen };
2759
2826
  }
@@ -1,6 +1,6 @@
1
+ import { CORE_TOOL_TURN_INCOMPLETE_CAUSES, CORE_USER_STEER_UNDRAINED_CAUSES } from './generated/engineNoticeTables.js';
1
2
  export type NoticeAudience = 'user' | 'operator';
2
3
  export declare const ENGINE_NOTICE_CODES: readonly string[];
3
- export declare const SERVER_NOTICE_AUDIENCE: Readonly<Record<string, NoticeAudience>>;
4
4
  export declare function engineNoticeInCatalog(code: unknown): boolean;
5
5
  export declare const ENGINE_NOTICE_AUDIENCE: Readonly<Record<string, NoticeAudience>>;
6
6
  export declare function noticeAudienceOf(code: unknown): NoticeAudience;
@@ -155,6 +155,26 @@ export interface ModelUnreachableFactsView {
155
155
  sessionId: string;
156
156
  }
157
157
  export declare function readModelUnreachable(notice: unknown): ModelUnreachableFactsView | undefined;
158
+ export type ToolTurnIncompleteCause = (typeof CORE_TOOL_TURN_INCOMPLETE_CAUSES)[number];
159
+ export interface ToolTurnIncompleteFactsView {
160
+ cause: string;
161
+ count: number;
162
+ toolCallIds?: readonly string[];
163
+ sessionId: string;
164
+ runId: string;
165
+ }
166
+ export declare function readToolTurnIncomplete(notice: unknown): ToolTurnIncompleteFactsView | undefined;
167
+ export type UserSteerUndrainedCause = (typeof CORE_USER_STEER_UNDRAINED_CAUSES)[number];
168
+ export interface UserInputUndrainedFactsView {
169
+ kind: 'steer' | 'followUp';
170
+ count: number;
171
+ cause?: string;
172
+ taskId?: string;
173
+ sessionId?: string;
174
+ runId?: string;
175
+ }
176
+ export declare function readUserInputUndrained(notice: unknown): UserInputUndrainedFactsView | undefined;
177
+ export declare function userInputUndrainedNotice(facts: Pick<UserInputUndrainedFactsView, 'kind' | 'count' | 'cause'>): string | undefined;
158
178
  export declare const ENGINE_NOTICE_FACT_READERS: Readonly<{
159
179
  readonly 'mcp.injection_dropped': typeof readMcpInjectionDrop;
160
180
  readonly 'delegation.ask_unresolvable': typeof readAskUnresolvable;
@@ -173,6 +193,9 @@ export declare const ENGINE_NOTICE_FACT_READERS: Readonly<{
173
193
  readonly 'resume.legacy_mode_seed_migrated': typeof readLegacyModeSeedMigrated;
174
194
  readonly 'context.tool_turn_repaired': typeof readToolTurnRepaired;
175
195
  readonly 'server.model_unreachable': typeof readModelUnreachable;
196
+ readonly 'task.tool_turn_incomplete': typeof readToolTurnIncomplete;
197
+ readonly 'task.user_steer_undrained': typeof readUserInputUndrained;
198
+ readonly 'task.user_followup_undrained': typeof readUserInputUndrained;
176
199
  }>;
177
200
  type FactReaderTable = typeof ENGINE_NOTICE_FACT_READERS;
178
201
  export type EngineNoticeFactsCode = keyof FactReaderTable;