@sema-agent/client-core 0.82.0 → 0.82.1

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +7 -5
  3. package/dist/adapt/toolCards.d.ts +15 -0
  4. package/dist/adapt/toolCards.js +34 -19
  5. package/dist/adapter/activeRunSelfHeal.d.ts +3 -0
  6. package/dist/adapter/activeRunSelfHeal.js +37 -7
  7. package/dist/adapter/downstream/eventToSdkMessage.js +7 -4
  8. package/dist/adapter/downstream/terminalToSdkResult.d.ts +2 -0
  9. package/dist/adapter/downstream/terminalToSdkResult.js +44 -5
  10. package/dist/adapter/runStream.js +33 -4
  11. package/dist/adapter/types.d.ts +2 -0
  12. package/dist/agentsWireCaps.d.ts +1 -0
  13. package/dist/agentsWireCaps.js +110 -29
  14. package/dist/decideFailureNote.d.ts +1 -0
  15. package/dist/decideFailureNote.js +8 -0
  16. package/dist/engineNoticeCodes.js +2 -0
  17. package/dist/executionLaneCapability.js +3 -0
  18. package/dist/fileHistoryCaptureCapability.d.ts +18 -0
  19. package/dist/fileHistoryCaptureCapability.js +58 -0
  20. package/dist/gateOutcome.js +2 -0
  21. package/dist/hitl/askGateWire.d.ts +1 -1
  22. package/dist/hitl/askGateWire.js +1 -1
  23. package/dist/hitl/hitlHostSurface.js +2 -3
  24. package/dist/hitl/parkResolver.d.ts +1 -0
  25. package/dist/hitl/parkResolver.js +11 -7
  26. package/dist/hitl/planReviewWire.js +7 -1
  27. package/dist/hitl/toolApprovalWire.js +3 -7
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.js +2 -0
  30. package/dist/modelIdentityResolvability.d.ts +31 -0
  31. package/dist/modelIdentityResolvability.js +145 -0
  32. package/dist/printToolResultFrame.js +15 -1
  33. package/dist/resumeRefusalCopy.d.ts +12 -0
  34. package/dist/resumeRefusalCopy.js +45 -1
  35. package/dist/seam.d.ts +2 -0
  36. package/dist/toolResult.d.ts +2 -2
  37. package/dist/toolResult.js +7 -6
  38. package/docs/INTEGRATION-CLIENTS.md +123 -10
  39. package/package.json +3 -3
@@ -1,4 +1,4 @@
1
- import { isCcToolDenialKind, isGateDeniedByWord } from '../../gateVocabulary.js';
1
+ import { isCcToolDenialKind, isCcToolDenialKindADenial, isGateDeniedByWord } from '../../gateVocabulary.js';
2
2
  import { stamp } from '../types.js';
3
3
  import { putOwnKey } from '../../ownKey.js';
4
4
  import { readEffectiveReasoning, readEffectiveMemoryScopes } from '../../effectiveFacts.js';
@@ -65,11 +65,10 @@ function permissionDenialParts(stats, observed) {
65
65
  const gateDeniedByByCallId = observed?.gateDeniedByByCallId;
66
66
  const denialKindByCallId = observed?.denialKindByCallId;
67
67
  const hr = stats?.humanReview;
68
- const gates = hr !== null && typeof hr === 'object' ? hr.gates : undefined;
69
- if (!Array.isArray(gates))
70
- return { permission_denials: [], _sema_permission_denials_absent: true };
68
+ const gatesRaw = hr !== null && typeof hr === 'object' ? hr.gates : undefined;
71
69
  const denials = [];
72
- let complete = true;
70
+ const gates = Array.isArray(gatesRaw) ? gatesRaw : [];
71
+ let complete = Array.isArray(gatesRaw);
73
72
  for (const g of gates) {
74
73
  if (g === null || typeof g !== 'object') {
75
74
  complete = false;
@@ -125,6 +124,43 @@ function permissionDenialParts(stats, observed) {
125
124
  ...(toolArg !== undefined ? { _sema_tool_arg: toolArg } : {}),
126
125
  });
127
126
  }
127
+ const deniedToolNameByCallId = observed?.deniedToolNameByCallId;
128
+ if (deniedToolNameByCallId !== undefined) {
129
+ const ledgerDeniedIds = new Set();
130
+ let ledgerHasIdlessDenial = false;
131
+ for (const d of denials) {
132
+ if (d.tool_use_id !== undefined)
133
+ ledgerDeniedIds.add(d.tool_use_id);
134
+ else
135
+ ledgerHasIdlessDenial = true;
136
+ }
137
+ for (const [callId, toolNameRaw] of deniedToolNameByCallId) {
138
+ if (typeof callId !== 'string' || callId.length === 0 || ledgerDeniedIds.has(callId))
139
+ continue;
140
+ const mergedKind = denialKindByCallId?.has(callId) === true ? denialKindByCallId.get(callId) : undefined;
141
+ if (isCcToolDenialKind(mergedKind) && !isCcToolDenialKindADenial(mergedKind))
142
+ continue;
143
+ if (ledgerHasIdlessDenial) {
144
+ complete = false;
145
+ continue;
146
+ }
147
+ const toolName = nonEmptyStr(toolNameRaw);
148
+ const joinedInput = toolInputByCallId?.has(callId) === true ? joinableToolInput(toolInputByCallId.get(callId)) : undefined;
149
+ const deniedByRaw = gateDeniedByByCallId?.has(callId) === true ? gateDeniedByByCallId.get(callId) : undefined;
150
+ const deniedBy = typeof deniedByRaw === 'string' && isGateDeniedByWord(deniedByRaw) ? deniedByRaw : undefined;
151
+ const toolDenialKind = isCcToolDenialKind(mergedKind) ? mergedKind : undefined;
152
+ denials.push({
153
+ ...(toolName !== undefined ? { tool_name: toolName } : {}),
154
+ tool_use_id: callId,
155
+ ...(joinedInput !== undefined ? { tool_input: joinedInput, _sema_tool_input_source: 'tool_start' } : {}),
156
+ ...(deniedBy !== undefined ? { deniedBy, _sema_denied_by_source: 'tool_end' } : {}),
157
+ ...(toolDenialKind !== undefined ? { toolDenialKind } : {}),
158
+ });
159
+ ledgerDeniedIds.add(callId);
160
+ }
161
+ if (observed?.deniedCallsOverflowed === true)
162
+ complete = false;
163
+ }
128
164
  const ccList = denials.filter((d) => d.tool_name !== undefined && d.tool_use_id !== undefined && d.tool_input !== undefined);
129
165
  const claimable = complete && ccList.length === denials.length;
130
166
  const zeroOutputDenied = denials.length > 0 && observed?.streamSawRunOpen === true && observed.successfulToolEndObserved === false;
@@ -200,15 +236,18 @@ function structuredOutputParts(r) {
200
236
  function effectiveFactParts(rec) {
201
237
  const reasoning = readEffectiveReasoning(rec.effectiveReasoning);
202
238
  const scopes = readEffectiveMemoryScopes(rec.effectiveMemoryScopes);
239
+ const responseModel = rec.responseModel;
203
240
  return {
204
241
  ...(reasoning !== undefined ? { _sema_effective_reasoning: reasoning } : {}),
205
242
  ...(scopes !== undefined ? { _sema_effective_memory_scopes: scopes } : {}),
243
+ ...(typeof responseModel === 'string' && responseModel.length > 0 ? { _sema_response_model: responseModel } : {}),
206
244
  };
207
245
  }
208
246
  function reemitEffectiveFacts(f) {
209
247
  const out = {
210
248
  ...(f?._sema_effective_reasoning !== undefined ? { _sema_effective_reasoning: f._sema_effective_reasoning } : {}),
211
249
  ...(f?._sema_effective_memory_scopes !== undefined ? { _sema_effective_memory_scopes: f._sema_effective_memory_scopes } : {}),
250
+ ...(f?._sema_response_model !== undefined ? { _sema_response_model: f._sema_response_model } : {}),
212
251
  };
213
252
  const _keySetPin = true;
214
253
  void _keySetPin;
@@ -6,7 +6,7 @@ import { coerceOutput, publishSubagentContentEvent } from '../subagentContentSto
6
6
  import { ACTIVE_RUN_BUSY_ERROR_CODE, OUTPUT_INVALID, isLimitsExceededCode } from '../engineErrorCodes.js';
7
7
  import { isReviewPark, readRunTerminal, runTerminalCode } from '../runTerminal.js';
8
8
  import { ccToolDenialKindForToolEnd, gateDeniedBy, gateOutcomeOf } from '../gateOutcome.js';
9
- import { isCcToolDenialKind, isGateDeniedByWord } from '../gateVocabulary.js';
9
+ import { isCcToolDenialKind, isCcToolDenialKindADenial, isGateDeniedByWord } from '../gateVocabulary.js';
10
10
  const mainLane = () => ({ lane: 'main' });
11
11
  function emitChromeFireAndForget(ctx, event) {
12
12
  try {
@@ -185,6 +185,8 @@ async function* runStreamInner(events, ctx, handle = {}) {
185
185
  const denialKindSourceByCallId = new Map();
186
186
  const denialAmbiguous = new Set();
187
187
  let denialJoinOverflowed = false;
188
+ const deniedToolNameByCallId = new Map();
189
+ let deniedCallsOverflowed = false;
188
190
  let successfulToolEndObserved = false;
189
191
  let streamSawRunOpen = false;
190
192
  const sameArgs = (a, b) => {
@@ -229,7 +231,8 @@ async function* runStreamInner(events, ctx, handle = {}) {
229
231
  if (ev.type === 'tool_end') {
230
232
  const endCallId = ev.toolCallId;
231
233
  if (typeof endCallId === 'string' && endCallId.length > 0) {
232
- const deniedByRaw = gateDeniedBy(gateOutcomeOf(ev));
234
+ const endGate = gateOutcomeOf(ev);
235
+ const deniedByRaw = gateDeniedBy(endGate);
233
236
  const deniedBy = isGateDeniedByWord(deniedByRaw) ? deniedByRaw : undefined;
234
237
  const kind = ccToolDenialKindForToolEnd(ev);
235
238
  const kindSource = kind === undefined
@@ -237,6 +240,23 @@ async function* runStreamInner(events, ctx, handle = {}) {
237
240
  : isCcToolDenialKind(ev._sema_denial_kind)
238
241
  ? 'local'
239
242
  : 'wire';
243
+ const deniedCall = endGate !== undefined
244
+ ? endGate.disposition.kind === 'denied' && (kind === undefined || isCcToolDenialKindADenial(kind))
245
+ : kind !== undefined && isCcToolDenialKindADenial(kind);
246
+ if (deniedCall) {
247
+ const endToolNameRaw = ev.toolName;
248
+ const endToolName = typeof endToolNameRaw === 'string' && endToolNameRaw.length > 0 ? endToolNameRaw : undefined;
249
+ if (deniedToolNameByCallId.has(endCallId)) {
250
+ if (deniedToolNameByCallId.get(endCallId) !== endToolName)
251
+ deniedToolNameByCallId.set(endCallId, undefined);
252
+ }
253
+ else if (deniedToolNameByCallId.size >= TOOL_INPUT_JOIN_MAX_CALLS) {
254
+ deniedCallsOverflowed = true;
255
+ }
256
+ else if (!deniedCallsOverflowed) {
257
+ deniedToolNameByCallId.set(endCallId, endToolName);
258
+ }
259
+ }
240
260
  if (deniedBy !== undefined || kind !== undefined) {
241
261
  const isNew = (deniedBy !== undefined && !gateDeniedByByCallId.has(endCallId)) ||
242
262
  (kind !== undefined && !denialKindByCallId.has(endCallId));
@@ -298,14 +318,18 @@ async function* runStreamInner(events, ctx, handle = {}) {
298
318
  output: coerceOutput(sub.output),
299
319
  isError: sub.isError,
300
320
  });
301
- continue;
321
+ if (!(ctx.lane === 'print' && (sub.type === 'tool_start' || sub.type === 'tool_end')))
322
+ continue;
302
323
  }
303
324
  if (ev.type === 'turn_end') {
304
325
  const usageMissing = ev.usageMissing === true;
305
326
  if (usageMissing)
306
327
  usageMissingObserved = true;
307
- const stopReasonRaw = ev.stopReason;
328
+ const turnEndLoose = ev;
329
+ const stopReasonRaw = turnEndLoose.stopReason;
308
330
  const stopWord = typeof stopReasonRaw === 'string' && stopReasonRaw.length > 0 ? stopReasonRaw : undefined;
331
+ const responseModelRaw = turnEndLoose.responseModel;
332
+ const responseModelWord = typeof responseModelRaw === 'string' && responseModelRaw.length > 0 ? responseModelRaw : undefined;
309
333
  const rawUsage = ev.usage;
310
334
  const engineUsage = typeof rawUsage === 'object' && rawUsage !== null && !Array.isArray(rawUsage)
311
335
  ? rawUsage
@@ -340,6 +364,7 @@ async function* runStreamInner(events, ctx, handle = {}) {
340
364
  engineUsage,
341
365
  ...(usageMissing ? { usageMissing: true } : {}),
342
366
  ...(stopWord !== undefined ? { stopReason: stopWord } : {}),
367
+ ...(responseModelWord !== undefined ? { responseModel: responseModelWord } : {}),
343
368
  });
344
369
  }
345
370
  }
@@ -362,6 +387,7 @@ async function* runStreamInner(events, ctx, handle = {}) {
362
387
  engineUsage,
363
388
  ...(usageMissing ? { usageMissing: true } : {}),
364
389
  ...(stopWord !== undefined ? { stopReason: stopWord } : {}),
390
+ ...(responseModelWord !== undefined ? { responseModel: responseModelWord } : {}),
365
391
  });
366
392
  }
367
393
  const rowKey = sourceTaskId !== undefined ? `s:${sourceTaskId}` : `p:${taskId}`;
@@ -389,6 +415,7 @@ async function* runStreamInner(events, ctx, handle = {}) {
389
415
  ? { outputTokens }
390
416
  : {}),
391
417
  ...(stopWord !== undefined ? { _sema_stop_reason: stopWord } : {}),
418
+ ...(responseModelWord !== undefined ? { _sema_response_model: responseModelWord } : {}),
392
419
  };
393
420
  }
394
421
  continue;
@@ -453,6 +480,8 @@ async function* runStreamInner(events, ctx, handle = {}) {
453
480
  denialKindByCallId,
454
481
  successfulToolEndObserved,
455
482
  streamSawRunOpen,
483
+ deniedToolNameByCallId,
484
+ deniedCallsOverflowed,
456
485
  });
457
486
  const outcome = resultFrame.
458
487
  _sema_outcome;
@@ -1,6 +1,7 @@
1
1
  import type { AgentEvent } from '@sema-agent/sdk';
2
2
  import type { ModelUsage, SDKMessage } from '@sema-agent/agent-types';
3
3
  import type { ChromeEvent } from '../seam.js';
4
+ import type { RequestLane } from '../request/taskRequest.js';
4
5
  export type { ModelUsage, SDKMessage };
5
6
  export type StampedAgentEvent = AgentEvent & {
6
7
  id?: string;
@@ -16,6 +17,7 @@ export interface EmitContext {
16
17
  sessionId: string;
17
18
  startedAtMs?: number;
18
19
  model?: string;
20
+ lane?: RequestLane;
19
21
  emitChrome?(event: ChromeEvent): void | Promise<void>;
20
22
  onDroppedFrame?(info: DroppedFrameInfo): void;
21
23
  }
@@ -69,6 +69,7 @@ export type WireConfig = {
69
69
  };
70
70
  export type ProjectorFn = () => Promise<ProjectAgentsResult>;
71
71
  export declare function prepareTaskAgentsWireWith(cfg: WireConfig, projector: ProjectorFn): Promise<void>;
72
+ export declare function invalidateTaskAgentsWire(): Promise<void>;
72
73
  export declare function awaitTaskAgentsWire(timeoutMs?: number): Promise<void>;
73
74
  export declare function taskAgentsField(): {
74
75
  agents?: TaskAgentWireItem[];
@@ -194,6 +194,7 @@ let preparedKey = null;
194
194
  let preparedPromise = null;
195
195
  let payload;
196
196
  let capsVerdict;
197
+ let settleGeneration = null;
197
198
  const emittedWarnings = new Set();
198
199
  function warnOnce(msg) {
199
200
  if (emittedWarnings.has(msg))
@@ -201,30 +202,57 @@ function warnOnce(msg) {
201
202
  emittedWarnings.add(msg);
202
203
  console.error(`[sema] ${msg}`);
203
204
  }
204
- async function runPrepare(cfg, projector) {
205
- try {
206
- const [caps, projected] = await Promise.all([
207
- engineSupportsTaskAgents(cfg.baseUrl, {
208
- authToken: cfg.authToken,
209
- principal: cfg.principal,
210
- }),
211
- projector(),
212
- ]);
213
- capsVerdict = caps;
214
- if (projected.agents && caps === false) {
215
- warnOnce(`agents wire: this engine does not support per-task custom agents (capabilities.taskAgents absent/false — server <1.205 or multi-tenant) — ${projected.agents.length} custom agent definition(s) will NOT be sent`);
216
- payload = undefined;
217
- return;
218
- }
205
+ function applyProjectionIfCurrent(generation, projected) {
206
+ if (generation === null || preparedPromise !== generation)
207
+ return false;
208
+ const accepted = capsVerdict !== false;
209
+ if (accepted) {
219
210
  for (const w of projected.warnings)
220
211
  warnOnce(w);
221
212
  payload = projected.agents;
213
+ }
214
+ settleGeneration?.();
215
+ return accepted;
216
+ }
217
+ function startCapsProbe(cfg) {
218
+ const land = (verdict) => {
219
+ if (capsVerdict !== probe)
220
+ return verdict;
221
+ capsVerdict = verdict;
222
+ if (verdict === false) {
223
+ payload = undefined;
224
+ settleGeneration?.();
225
+ }
226
+ return verdict;
227
+ };
228
+ const probe = engineSupportsTaskAgents(cfg.baseUrl, {
229
+ authToken: cfg.authToken,
230
+ principal: cfg.principal,
231
+ }).then(land, () => land(undefined));
232
+ capsVerdict = probe;
233
+ return probe;
234
+ }
235
+ function capsSettled() {
236
+ return capsVerdict instanceof Promise ? capsVerdict : Promise.resolve();
237
+ }
238
+ async function runPrepare(cfg, projector, probe, generation) {
239
+ try {
240
+ const [caps, projected] = await Promise.all([probe, projector()]);
241
+ if (caps === false) {
242
+ if (projected.agents && preparedKey === cfg.baseUrl) {
243
+ warnOnce(`agents wire: this engine does not support per-task custom agents (capabilities.taskAgents absent/false — server <1.205 or multi-tenant) — ${projected.agents.length} custom agent definition(s) will NOT be sent`);
244
+ }
245
+ return;
246
+ }
247
+ if (!applyProjectionIfCurrent(generation, projected))
248
+ return;
222
249
  if (payload && hostEnv().SEMA_DEBUG) {
223
250
  console.error(`[sema] agents wire: ${payload.length} custom agent(s) → TaskRequest.agents (${payload.map(a => a.name).join(', ')})`);
224
251
  }
225
252
  }
226
253
  catch (e) {
227
- payload = undefined;
254
+ if (preparedPromise === generation)
255
+ payload = undefined;
228
256
  if (hostEnv().SEMA_DEBUG) {
229
257
  console.error('[sema] agents wire prepare soft-failed (no agents stamped):', e);
230
258
  }
@@ -235,21 +263,66 @@ export function prepareTaskAgentsWireWith(cfg, projector) {
235
263
  if (preparedPromise && preparedKey === key)
236
264
  return preparedPromise;
237
265
  preparedKey = key;
238
- preparedPromise = runPrepare(cfg, projector);
239
266
  activeProjector = projector;
240
- return preparedPromise;
267
+ const probe = startCapsProbe(cfg);
268
+ return startGeneration(generation => runPrepare(cfg, projector, probe, generation));
269
+ }
270
+ function startGeneration(run) {
271
+ let settle;
272
+ const generation = new Promise(resolve => {
273
+ settle = resolve;
274
+ });
275
+ const superseded = settleGeneration;
276
+ preparedPromise = generation;
277
+ settleGeneration = settle;
278
+ superseded?.();
279
+ void run(generation).then(settle, settle);
280
+ return generation;
281
+ }
282
+ export function invalidateTaskAgentsWire() {
283
+ const projector = activeProjector;
284
+ if (!preparedPromise || !projector)
285
+ return Promise.resolve();
286
+ payload = undefined;
287
+ return startGeneration(generation => runReproject(projector, generation));
288
+ }
289
+ async function runReproject(projector, generation) {
290
+ try {
291
+ let projected;
292
+ try {
293
+ projected = await projector();
294
+ }
295
+ catch (e) {
296
+ if (hostEnv().SEMA_DEBUG) {
297
+ console.error('[sema] agents wire re-project soft-failed (no agents stamped until the next successful projection):', e);
298
+ }
299
+ return;
300
+ }
301
+ await capsSettled();
302
+ applyProjectionIfCurrent(generation, projected);
303
+ }
304
+ catch {
305
+ }
241
306
  }
242
307
  export async function awaitTaskAgentsWire(timeoutMs = 4000) {
243
308
  if (!preparedPromise)
244
309
  return;
310
+ if (capsVerdict === false)
311
+ return;
245
312
  let timeoutHandle;
313
+ let timedOut = false;
314
+ const deadline = new Promise(resolve => {
315
+ timeoutHandle = setTimeout(() => {
316
+ timedOut = true;
317
+ resolve();
318
+ }, timeoutMs);
319
+ });
246
320
  try {
247
- await Promise.race([
248
- preparedPromise.catch(() => { }),
249
- new Promise(resolve => {
250
- timeoutHandle = setTimeout(resolve, timeoutMs);
251
- }),
252
- ]);
321
+ let waited = null;
322
+ while (!timedOut && preparedPromise !== null && preparedPromise !== waited) {
323
+ waited = preparedPromise;
324
+ await Promise.race([waited.catch(() => { }), deadline]);
325
+ }
253
326
  }
254
327
  finally {
255
328
  if (timeoutHandle !== undefined)
@@ -259,11 +332,18 @@ export async function awaitTaskAgentsWire(timeoutMs = 4000) {
259
332
  export function taskAgentsField() {
260
333
  const current = payload;
261
334
  if (preparedPromise && capsVerdict !== false && activeProjector) {
262
- void activeProjector()
263
- .then(p => {
264
- for (const w of p.warnings)
265
- warnOnce(w);
266
- payload = p.agents;
335
+ const generation = preparedPromise;
336
+ let refreshing;
337
+ try {
338
+ refreshing = activeProjector();
339
+ }
340
+ catch {
341
+ return current ? { agents: current } : {};
342
+ }
343
+ void refreshing
344
+ .then(async (p) => {
345
+ await capsSettled();
346
+ applyProjectionIfCurrent(generation, p);
267
347
  })
268
348
  .catch(() => { });
269
349
  }
@@ -273,6 +353,7 @@ export function _resetAgentsWireForTest() {
273
353
  activeProjector = null;
274
354
  preparedKey = null;
275
355
  preparedPromise = null;
356
+ settleGeneration = null;
276
357
  payload = undefined;
277
358
  capsVerdict = undefined;
278
359
  emittedWarnings.clear();
@@ -0,0 +1 @@
1
+ export declare function decideFailureNote(err: unknown): string;
@@ -0,0 +1,8 @@
1
+ import { corruptStoredRowContent, corruptStoredRowWhere } from './wireErrorTriage.js';
2
+ import { resumePolicyBlockContent, resumePolicyBlockFromError } from './resumeRefusalCopy.js';
3
+ export function decideFailureNote(err) {
4
+ const where = corruptStoredRowWhere(err);
5
+ const fold = resumePolicyBlockFromError(err);
6
+ return ((where === undefined ? '' : ` — ${corruptStoredRowContent(where)}`) +
7
+ (fold === null ? '' : ` — ${resumePolicyBlockContent(fold)}`));
8
+ }
@@ -55,6 +55,7 @@ export const ENGINE_NOTICE_CODES = Object.freeze([
55
55
  'route.fallback_to_primary',
56
56
  'route.classifier_unroutable',
57
57
  'route.base_url_changed_key_unchanged',
58
+ 'route.response_model_mismatch',
58
59
  'task.user_steer_undrained',
59
60
  'task.user_followup_undrained',
60
61
  'steering.parked_input_blocked',
@@ -129,6 +130,7 @@ export const ENGINE_NOTICE_AUDIENCE = Object.freeze({
129
130
  'route.fallback_to_primary': 'operator',
130
131
  'route.classifier_unroutable': 'operator',
131
132
  'route.base_url_changed_key_unchanged': 'operator',
133
+ 'route.response_model_mismatch': 'operator',
132
134
  'task.user_steer_undrained': 'user',
133
135
  'task.user_followup_undrained': 'user',
134
136
  'steering.parked_input_blocked': 'user',
@@ -51,6 +51,9 @@ export function executionLaneDoctorDetail(reading) {
51
51
  if (reading.view.toolsOnThisHost) {
52
52
  return `execution lane ${lane} — tools run on the engine host (same filesystem), so skills that have a directory of their own can carry a baseDir`;
53
53
  }
54
+ if (reading.view.provider === 'in-process') {
55
+ return 'no execution lane on this deployment (in-process) — the engine has no machine of its own for running tools here, so its built-in file and shell tools are not mounted and skill baseDir is never sent; whether a given run had tools is answered by its tool roster';
56
+ }
54
57
  return `execution lane ${lane} — tools run on a different machine than the engine, so skill baseDir is never sent on this deployment`;
55
58
  }
56
59
  }
@@ -0,0 +1,18 @@
1
+ export type FileHistoryCaptureReading = {
2
+ kind: 'unobserved';
3
+ } | {
4
+ kind: 'not_reported';
5
+ } | {
6
+ kind: 'present';
7
+ word: string;
8
+ };
9
+ export type FileHistoryCaptureMode = 'off' | 'on-always' | 'unknown';
10
+ export declare function projectFileHistoryCaptureCapability(caps: unknown): FileHistoryCaptureReading | undefined;
11
+ export declare function noteEngineCapsForFileHistoryCapture(baseUrl: string, caps: unknown, opts?: {
12
+ generation?: number;
13
+ }): void;
14
+ export declare function observedFileHistoryCapture(baseUrl?: string | undefined): FileHistoryCaptureReading;
15
+ export declare function fileHistoryCaptureMode(reading: FileHistoryCaptureReading): FileHistoryCaptureMode;
16
+ export declare function fileHistoryCaptureDoctorDetail(reading: FileHistoryCaptureReading): string;
17
+ export declare function forgetFileHistoryCaptureReading(baseUrl: string | undefined): void;
18
+ export declare function __resetFileHistoryCaptureReadingsForTests(): void;
@@ -0,0 +1,58 @@
1
+ import { capForDisplay } from './fleetTaskDesc.js';
2
+ import { engineWireTarget } from './engineWireTarget.js';
3
+ import { createEngineCapReader } from './engineCapReader.js';
4
+ export function projectFileHistoryCaptureCapability(caps) {
5
+ if (caps === null || typeof caps !== 'object' || Array.isArray(caps))
6
+ return undefined;
7
+ if (!Object.hasOwn(caps, 'fileHistoryCapture'))
8
+ return { kind: 'not_reported' };
9
+ const v = caps.fileHistoryCapture;
10
+ if (v === undefined)
11
+ return { kind: 'not_reported' };
12
+ if (typeof v !== 'string' || v.length === 0)
13
+ return undefined;
14
+ return { kind: 'present', word: v };
15
+ }
16
+ const reader = createEngineCapReader({
17
+ name: 'fileHistoryCapture',
18
+ project: projectFileHistoryCaptureCapability,
19
+ makeUnobserved: () => ({ kind: 'unobserved' }),
20
+ });
21
+ export function noteEngineCapsForFileHistoryCapture(baseUrl, caps, opts) {
22
+ reader.note(baseUrl, caps, opts);
23
+ }
24
+ export function observedFileHistoryCapture(baseUrl = engineWireTarget()?.baseUrl) {
25
+ return reader.observed(baseUrl);
26
+ }
27
+ export function fileHistoryCaptureMode(reading) {
28
+ if (reading.kind !== 'present')
29
+ return 'unknown';
30
+ if (reading.word === 'off')
31
+ return 'off';
32
+ if (reading.word === 'on-always')
33
+ return 'on-always';
34
+ return 'unknown';
35
+ }
36
+ const WORD_DETAIL_MAX = 40;
37
+ export function fileHistoryCaptureDoctorDetail(reading) {
38
+ switch (reading.kind) {
39
+ case 'unobserved':
40
+ return 'file history capture not observed — the engine reports it on /v1/capabilities; this process has no usable capabilities reading cached for it, so whether this deployment captures file history is unknown';
41
+ case 'not_reported':
42
+ return 'file history capture not reported by this engine — only newer engines advertise it; this does not say whether file history is captured here';
43
+ case 'present':
44
+ if (reading.word === 'off') {
45
+ return 'file history capture off on this deployment — the engine takes no snapshots of the files it changes; whether code can be rewound here is answered separately by the rewind readings';
46
+ }
47
+ if (reading.word === 'on-always') {
48
+ return 'file history capture always on — the engine snapshots files before changing them in every session, and it cannot be turned off for a single session here';
49
+ }
50
+ return `file history capture reported as "${capForDisplay(reading.word, WORD_DETAIL_MAX)}" — a mode this client does not know yet, so whether it captures is shown as unknown`;
51
+ }
52
+ }
53
+ export function forgetFileHistoryCaptureReading(baseUrl) {
54
+ reader.forget(baseUrl);
55
+ }
56
+ export function __resetFileHistoryCaptureReadingsForTests() {
57
+ reader.__resetForTests();
58
+ }
@@ -146,6 +146,8 @@ export function ccToolDenialKindForToolEnd(frame) {
146
146
  const stamped = frame._sema_denial_kind;
147
147
  if (isCcToolDenialKind(stamped))
148
148
  return stamped;
149
+ if (frame._sema_denial_unclassified === true)
150
+ return undefined;
149
151
  const outcome = gateOutcomeOf(frame);
150
152
  const kind = outcome?.settlement?.kind;
151
153
  if (kind === 'human_refused')
@@ -8,7 +8,7 @@ export type { AskAnsweredOutput } from './gateLedger.js';
8
8
  export { SEMA_COLLATERAL_ABORT_KEY } from './gateLedger.js';
9
9
  export { SEMA_DENIAL_KIND_KEY } from './gateLedger.js';
10
10
  export type { GateDenyAttribution } from './gateLedger.js';
11
- export { toAnsweredOutput, isAlreadyResolvedGateReason, GATE_FAILURE_CODES, nextHopBudget, MAX_GATE_HOPS, MAX_TOTAL_PARKS } from './parkResolver.js';
11
+ export { toAnsweredOutput, isAlreadyResolvedGateReason, isGateStandingErrorCode, GATE_FAILURE_CODES, nextHopBudget, MAX_GATE_HOPS, MAX_TOTAL_PARKS } from './parkResolver.js';
12
12
  export type { HopBudget, HopRound } from './parkResolver.js';
13
13
  export type { GateOutcome, GateFailureCode } from './parkResolver.js';
14
14
  export declare function bridgeAskUserQuestionGates(source: AsyncGenerator<AgentEvent>, deps: AskGateWireDeps, opts?: {
@@ -6,7 +6,7 @@ export { HITL_INTERRUPT_MESSAGE_FOR_TOOL_USE } from './frameRouter.js';
6
6
  export { HITL_POLICY_DENY_MESSAGE } from './frameRouter.js';
7
7
  export { SEMA_COLLATERAL_ABORT_KEY } from './gateLedger.js';
8
8
  export { SEMA_DENIAL_KIND_KEY } from './gateLedger.js';
9
- export { toAnsweredOutput, isAlreadyResolvedGateReason, GATE_FAILURE_CODES, nextHopBudget, MAX_GATE_HOPS, MAX_TOTAL_PARKS } from './parkResolver.js';
9
+ export { toAnsweredOutput, isAlreadyResolvedGateReason, isGateStandingErrorCode, GATE_FAILURE_CODES, nextHopBudget, MAX_GATE_HOPS, MAX_TOTAL_PARKS } from './parkResolver.js';
10
10
  export async function* bridgeAskUserQuestionGates(source, deps, opts) {
11
11
  const led = createGateLedger();
12
12
  const taskId = { current: opts?.taskId };
@@ -1,7 +1,7 @@
1
1
  import { createSessionSlot, DEFAULT_SESSION_KEY } from '../sessionSlot.js';
2
2
  import { hostLog } from '../host.js';
3
3
  import { unrefTimer } from '../unrefTimer.js';
4
- import { corruptStoredRowContent, corruptStoredRowWhere } from '../wireErrorTriage.js';
4
+ import { decideFailureNote } from '../decideFailureNote.js';
5
5
  import { HitlSafetyError } from './hitlBridge.js';
6
6
  const hostSurfaceByKey = createSessionSlot();
7
7
  const hostSurfaceMissesByKey = new Map();
@@ -113,8 +113,7 @@ export function observeCancelByDeny(settle, taskId) {
113
113
  hostLog('debug', `[hitl-cancel-deny] cancel-by-deny no_pending for run ${taskId} (benign — already resolved)`);
114
114
  return;
115
115
  }
116
- const where = corruptStoredRowWhere(e);
117
- const rowNote = where === undefined ? '' : ` — ${corruptStoredRowContent(where)}`;
116
+ const rowNote = decideFailureNote(e);
118
117
  hostLog('error', `[hitl-cancel-deny] cancel-by-deny FAILED for run ${taskId}: ${String(e)}${rowNote}`);
119
118
  surfaceCancelDenyWarn(`${CANCEL_DENY_WARN_TEXT}${rowNote}`);
120
119
  })
@@ -33,6 +33,7 @@ export type GateOutcome = {
33
33
  currentPending?: GateCurrentPending | undefined;
34
34
  };
35
35
  export declare function isAlreadyResolvedGateReason(reason: unknown): boolean;
36
+ export declare function isGateStandingErrorCode(code: string | null | undefined): boolean;
36
37
  export declare function toAnsweredOutput(questions: unknown[], answer: QuestionAnswer): AskAnsweredOutput;
37
38
  export type ParkResolution = {
38
39
  kind: 'reattach';
@@ -1,5 +1,6 @@
1
1
  import { putOwnKey } from '../ownKey.js';
2
2
  import { DecideTransportRetryExhaustedError, HitlBridge, HitlSafetyError, findPendingForTask, readDecideCurrentPending, readWireErrorCode, } from './hitlBridge.js';
3
+ import { RESUME_POLICY_FOLD_CODE } from '../engineErrorCodes.js';
3
4
  import { publishQuestionFrame, registerLocalQuestionResponder, hasQuestionOverlay, } from '../liveQuestionStore.js';
4
5
  import { hostLog } from '../host.js';
5
6
  import { surfaceFsApprovalAndDecide } from './toolApprovalWire.js';
@@ -7,7 +8,7 @@ import { observeCancelByDeny } from './hitlHostSurface.js';
7
8
  import { flushHeldWithInterruptRewrite, isAskTool } from './frameRouter.js';
8
9
  import { approvalCallKey, askGateQuestionId } from './gateIdentity.js';
9
10
  import { readDecideReceipt } from '../decideReceipt.js';
10
- import { corruptStoredRowContent, corruptStoredRowWhere } from '../wireErrorTriage.js';
11
+ import { decideFailureNote } from '../decideFailureNote.js';
11
12
  export const MAX_GATE_HOPS = 2;
12
13
  export function nextHopBudget(prev, round) {
13
14
  const total = (prev?.total ?? 0) + 1;
@@ -41,7 +42,14 @@ function alreadyResolvedReasonToken(reason) {
41
42
  }
42
43
  function isAlreadyResolvedFailure(outcome) {
43
44
  const code = 'code' in outcome && isGateFailureCode(outcome.code) ? outcome.code : undefined;
44
- return code !== undefined ? code === 'no_pending' : isAlreadyResolvedGateReason(outcome.reason);
45
+ if (code !== undefined)
46
+ return code === 'no_pending';
47
+ if ('errorCode' in outcome && isGateStandingErrorCode(outcome.errorCode))
48
+ return false;
49
+ return isAlreadyResolvedGateReason(outcome.reason);
50
+ }
51
+ export function isGateStandingErrorCode(code) {
52
+ return code === RESUME_POLICY_FOLD_CODE || code === 'corrupt';
45
53
  }
46
54
  export function toAnsweredOutput(questions, answer) {
47
55
  const answers = {};
@@ -141,7 +149,7 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
141
149
  kind: 'failed',
142
150
  stage: 'decide',
143
151
  gatedCallId,
144
- reason: withCorruptStoredRow(`decide failed: ${String(e)}`, e),
152
+ reason: `decide failed: ${String(e)}${decideFailureNote(e)}`,
145
153
  ...(code !== undefined ? { code } : {}),
146
154
  ...(e instanceof DecideTransportRetryExhaustedError ? { retryExhausted: true } : {}),
147
155
  ...(currentPending !== undefined ? { currentPending } : {}),
@@ -149,10 +157,6 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
149
157
  };
150
158
  }
151
159
  }
152
- function withCorruptStoredRow(reason, err) {
153
- const where = corruptStoredRowWhere(err);
154
- return where === undefined ? reason : `${reason} — ${corruptStoredRowContent(where)}`;
155
- }
156
160
  function argsByCallOf(led) {
157
161
  return new Map(led.gatedStartArgs());
158
162
  }