@sema-agent/client-core 0.82.6 → 0.83.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +11 -5
  3. package/dist/adapt/arms.d.ts +3 -2
  4. package/dist/adapt/arms.js +16 -11
  5. package/dist/adapt/ids.js +12 -8
  6. package/dist/adapt/textStream.d.ts +3 -2
  7. package/dist/adapt/toolCards.d.ts +2 -3
  8. package/dist/adapt/toolCards.js +1 -1
  9. package/dist/adapt.d.ts +1 -1
  10. package/dist/adapt.js +13 -6
  11. package/dist/adapter/activeRunSelfHeal.js +34 -17
  12. package/dist/adapter/downstream/eventToSdkMessage.js +1 -1
  13. package/dist/adapter/downstream/terminalToSdkResult.d.ts +2 -4
  14. package/dist/adapter/downstream/terminalToSdkResult.js +43 -5
  15. package/dist/adapter/runStream.js +6 -6
  16. package/dist/clientSlice.d.ts +0 -5
  17. package/dist/engineNoticeCodes.js +4 -0
  18. package/dist/fleet/fleetRowAgentType.d.ts +0 -1
  19. package/dist/fleet/fleetRowAgentType.js +0 -3
  20. package/dist/hitl/approvalResolution.js +2 -0
  21. package/dist/hitl/frameRouter.d.ts +3 -2
  22. package/dist/hitl/toolApprovalWire.d.ts +1 -0
  23. package/dist/hitl/toolApprovalWire.js +56 -10
  24. package/dist/hooksWireCaps.js +27 -1
  25. package/dist/host.d.ts +1 -0
  26. package/dist/liveInitToolFace.d.ts +4 -2
  27. package/dist/liveInitToolFace.js +2 -2
  28. package/dist/model/catalogLoader.d.ts +1 -6
  29. package/dist/panelRunningHistory.d.ts +3 -2
  30. package/dist/peerFrames.d.ts +0 -1
  31. package/dist/printInitToolFace.js +1 -1
  32. package/dist/request/printNotification.js +26 -26
  33. package/dist/seam.d.ts +8 -2
  34. package/dist/seam.js +18 -7
  35. package/dist/systemReminderTag.d.ts +0 -1
  36. package/dist/systemReminderTag.js +0 -3
  37. package/dist/toolResult.js +1 -0
  38. package/dist/uuidV5.d.ts +3 -0
  39. package/dist/uuidV5.js +178 -0
  40. package/dist/webSearchWireCaps.d.ts +20 -3
  41. package/dist/webSearchWireCaps.js +193 -25
  42. package/dist/wireFailureShape.d.ts +2 -1
  43. package/docs/INTEGRATION-CLIENTS.md +463 -12
  44. package/package.json +2 -2
@@ -161,7 +161,7 @@ export function eventToSdkMessage(ev, ctx) {
161
161
  : {}),
162
162
  ...(freedTokens !== undefined ? { _sema_freed_tokens: freedTokens } : {}),
163
163
  },
164
- ...(Array.isArray(attachedFiles) && attachedFiles.length > 0 ? { attachedFiles } : {}),
164
+ ...(Array.isArray(attachedFiles) && attachedFiles.length > 0 ? { _sema_attached_files: attachedFiles } : {}),
165
165
  })));
166
166
  }
167
167
  case 'tool_end': {
@@ -1,6 +1,7 @@
1
1
  import type { AgentEvent, DeniedBy, TaskStats } from '@sema-agent/sdk';
2
2
  import { type CcToolDenialKind } from '../../gateVocabulary.js';
3
3
  import { type SDKMessage, type EmitContext } from '../types.js';
4
+ import type { TerminalReason } from '@sema-agent/agent-types';
4
5
  import { type SemaModelUsage } from './turnUsageToModelUsage.js';
5
6
  export interface SemaFlatUsage {
6
7
  readonly inputTokens: number;
@@ -90,15 +91,12 @@ export declare function subagentUsageIsPartial(row: {
90
91
  } | null | undefined, opts?: {
91
92
  readonly tablePartial?: boolean;
92
93
  }): boolean;
93
- export interface SemaNestedUsageByTask {
94
- readonly rows: Readonly<Record<string, SemaSubagentUsageRow>>;
95
- readonly partial: boolean;
96
- }
97
94
  export interface SemaTerminalModelUsage extends SemaModelUsage {
98
95
  readonly _sema_total_input_tokens?: number;
99
96
  readonly _sema_usage_basis?: string;
100
97
  readonly _sema_cache_write_tokens_long?: number;
101
98
  }
99
+ export declare function terminalReasonForResult(msg: SDKMessage): TerminalReason | undefined;
102
100
  export declare function doneToSdkResult(ev: Extract<AgentEvent, {
103
101
  type: 'done';
104
102
  }>, ctx: EmitContext, observed?: TerminalStreamObserved): SDKMessage;
@@ -413,6 +413,41 @@ function subtypeForErrorCode(code) {
413
413
  return 'error_max_structured_output_retries';
414
414
  return 'error_during_execution';
415
415
  }
416
+ function terminalReasonOf(subtype, isError) {
417
+ switch (subtype) {
418
+ case 'success':
419
+ return isError === false ? 'completed' : undefined;
420
+ case 'error_max_turns':
421
+ return 'max_turns';
422
+ case 'error_max_budget_usd':
423
+ return 'budget_exhausted';
424
+ case 'error_max_structured_output_retries':
425
+ return 'structured_output_retry_exhausted';
426
+ default:
427
+ return undefined;
428
+ }
429
+ }
430
+ function terminalReasonParts(subtype, isError) {
431
+ const reason = terminalReasonOf(subtype, isError);
432
+ return reason !== undefined ? { terminal_reason: reason } : {};
433
+ }
434
+ export function terminalReasonForResult(msg) {
435
+ try {
436
+ const m = msg;
437
+ if (typeof m !== 'object' || m === null || Array.isArray(m))
438
+ return undefined;
439
+ const rec = m;
440
+ if (rec.type !== 'result')
441
+ return undefined;
442
+ return terminalReasonOf(rec.subtype, rec.is_error);
443
+ }
444
+ catch {
445
+ return undefined;
446
+ }
447
+ }
448
+ function selectedModelParts(model) {
449
+ return typeof model === 'string' && model.length > 0 ? { _sema_selected_model: model } : {};
450
+ }
416
451
  function salvagedTextOf(r) {
417
452
  const salvagedOutput = r.salvagedOutput;
418
453
  return typeof r.result === 'string' && r.result.trim().length > 0
@@ -449,9 +484,11 @@ function errorResult(ctx, parts) {
449
484
  ...reemitEffectiveFacts(parts.facts),
450
485
  ...nestedUsageByTaskParts(parts.stats, parts.observed?.nestedUsageByTask),
451
486
  errors: [...parts.errors],
452
- ...(parts.errorCode !== undefined && parts.errorCode.length > 0 ? { errorCode: parts.errorCode } : {}),
453
- ...(parts.degraded !== undefined ? { degraded: parts.degraded } : {}),
454
- ...(parts.salvagedResult !== undefined ? { result: parts.salvagedResult } : {}),
487
+ ...terminalReasonParts(parts.subtype, true),
488
+ ...(parts.errorCode !== undefined && parts.errorCode.length > 0 ? { _sema_error_code: parts.errorCode } : {}),
489
+ ...(parts.degraded !== undefined ? { _sema_model_degraded: parts.degraded } : {}),
490
+ ...selectedModelParts(parts.model),
491
+ ...(parts.salvagedResult !== undefined ? { _sema_salvaged_result: parts.salvagedResult } : {}),
455
492
  ...(parts.outcomeUnknown === true ? { _sema_outcome: 'unknown' } : {}),
456
493
  });
457
494
  }
@@ -556,16 +593,17 @@ export function doneToSdkResult(ev, ctx, observed) {
556
593
  num_turns: stats?.turns ?? 0,
557
594
  result: r.result ?? '',
558
595
  ...structuredOutputParts(r),
559
- ...(degraded !== undefined ? { degraded } : {}),
596
+ ...(degraded !== undefined ? { _sema_model_degraded: degraded } : {}),
560
597
  stop_reason: null,
561
598
  total_cost_usd: costOrNull(stats),
562
599
  usage: flattenUsage(stats),
563
600
  modelUsage: modelUsageFor(stats, r.model),
564
601
  ...permissionDenialParts(stats, observed),
602
+ ...terminalReasonParts('success', false),
565
603
  ...costFactParts(stats, observed),
566
604
  ...effectiveFactParts(rec),
567
605
  ...nestedUsageByTaskParts(stats, observed?.nestedUsageByTask),
568
- ...(typeof r.model === 'string' ? { model: r.model } : {}),
606
+ ...selectedModelParts(r.model),
569
607
  });
570
608
  }
571
609
  export function failedToSdkResult(ev, ctx, observed) {
@@ -1,4 +1,4 @@
1
- import { eventSeq, } from './types.js';
1
+ import { eventSeq, stamp, } from './types.js';
2
2
  import { eventToSdkMessage, turnEndUsage } from './downstream/eventToSdkMessage.js';
3
3
  import { turnUsageToModelUsage } from './downstream/turnUsageToModelUsage.js';
4
4
  import { readRunCostFacts, terminalToSdkResult, TOOL_INPUT_JOIN_MAX_CALLS } from './downstream/terminalToSdkResult.js';
@@ -97,14 +97,14 @@ export function isModelOutputErrorRowText(text) {
97
97
  }
98
98
  export const OUTCOME_UNKNOWN_ROW_PREFIX = 'Outcome unknown';
99
99
  function syntheticTerminalRow(ctx, text) {
100
- return {
101
- session_id: ctx.sessionId ?? '',
102
- uuid: `err-${Date.now().toString(36)}`,
100
+ return stamp(ctx, {
101
+ uuid: undefined,
102
+ session_id: undefined,
103
103
  type: 'assistant',
104
104
  message: { role: 'assistant', model: '<synthetic>', content: [{ type: 'text', text }] },
105
105
  parent_tool_use_id: null,
106
- isApiErrorMessage: true,
107
- };
106
+ _sema_api_error_message: true,
107
+ });
108
108
  }
109
109
  export function isOutcomeUnknownRowText(text) {
110
110
  return text.trimStart().startsWith(`${OUTCOME_UNKNOWN_ROW_PREFIX}:`);
@@ -1,8 +1,3 @@
1
- export interface ClientVerbSpec {
2
- verb: string;
3
- required: boolean;
4
- duty: string;
5
- }
6
1
  export interface ClientSliceLike {
7
2
  streamTask(req: unknown, opts?: {
8
3
  signal?: {
@@ -3,6 +3,7 @@ export const ENGINE_NOTICE_CODES = Object.freeze([
3
3
  'config.env_timeout_discarded',
4
4
  'config.materialize_env_discarded',
5
5
  'config.models_swapped',
6
+ 'config.context_settings_swapped',
6
7
  'config.read_face_swapped',
7
8
  'config.read_face_deployment_clamped',
8
9
  'config.write_face_swapped',
@@ -22,6 +23,7 @@ export const ENGINE_NOTICE_CODES = Object.freeze([
22
23
  'config.durable_gate_unavailable',
23
24
  'config.peer_admission_out_of_range',
24
25
  'config.peer_lane_unmounted',
26
+ 'config.secret_env_scrubbed',
25
27
  'peer.inbound_disposition',
26
28
  'peer.held_settled',
27
29
  'peer.idle_subscription',
@@ -78,6 +80,7 @@ export const ENGINE_NOTICE_AUDIENCE = Object.freeze({
78
80
  'config.env_timeout_discarded': 'operator',
79
81
  'config.materialize_env_discarded': 'operator',
80
82
  'config.models_swapped': 'operator',
83
+ 'config.context_settings_swapped': 'operator',
81
84
  'config.read_face_swapped': 'operator',
82
85
  'config.read_face_deployment_clamped': 'operator',
83
86
  'config.write_face_swapped': 'operator',
@@ -97,6 +100,7 @@ export const ENGINE_NOTICE_AUDIENCE = Object.freeze({
97
100
  'config.durable_gate_unavailable': 'user',
98
101
  'config.peer_admission_out_of_range': 'operator',
99
102
  'config.peer_lane_unmounted': 'operator',
103
+ 'config.secret_env_scrubbed': 'operator',
100
104
  'peer.inbound_disposition': 'user',
101
105
  'peer.held_settled': 'user',
102
106
  'peer.idle_subscription': 'user',
@@ -1,3 +1,2 @@
1
1
  export declare function recordFleetRowAgentType(taskIdTail: string, agentType: string | undefined): void;
2
2
  export declare function fleetRowAgentType(taskIdTail: string | undefined): string | undefined;
3
- export declare function __resetFleetRowAgentTypeForTests(): void;
@@ -23,6 +23,3 @@ export function fleetRowAgentType(taskIdTail) {
23
23
  agentTypeByTail.set(taskIdTail, v);
24
24
  return v;
25
25
  }
26
- export function __resetFleetRowAgentTypeForTests() {
27
- agentTypeByTail.clear();
28
- }
@@ -70,6 +70,8 @@ function fsLegResolution(outcome, kind) {
70
70
  }
71
71
  if (stage === 'input')
72
72
  return { kind: 'not_sent', cause: 'no_input', ...call, ...detail };
73
+ if (stage === 'card' && pick(outcome, 'editRefused') === true)
74
+ return { kind: 'not_sent', cause: 'edit_refused', ...call, ...detail };
73
75
  if (stage === 'card')
74
76
  return { kind: 'not_sent', cause: 'card_unavailable', ...call, ...detail };
75
77
  if (stage === 'decide') {
@@ -24,7 +24,7 @@ export interface AskGateWireDeps {
24
24
  onToolApprovalOutcome?: (frame: ToolApprovalFrame, outcome: ToolApprovalFrameOutcome) => void | Promise<void>;
25
25
  onDecideOperation?: DecideOperationObserver;
26
26
  }
27
- export type GateKind = 'ask' | 'fs';
27
+ type GateKind = 'ask' | 'fs';
28
28
  export interface GatePark {
29
29
  gate: GateKind;
30
30
  pendingDone?: AgentEvent | undefined;
@@ -55,10 +55,11 @@ export interface FrameRouterCtx {
55
55
  }
56
56
  export declare function isAskTool(toolName: string | undefined): boolean;
57
57
  export declare function isHostProgressFrame(ev: AgentEvent): boolean;
58
- export interface HeldFlushInterruptEvidence {
58
+ interface HeldFlushInterruptEvidence {
59
59
  terminal?: AgentEvent | undefined;
60
60
  signal?: AbortSignal | undefined;
61
61
  gateAbortedByUser?: boolean | undefined;
62
62
  }
63
63
  export declare function flushHeldWithInterruptRewrite(led: GateLedger, evidence: HeldFlushInterruptEvidence): Generator<AgentEvent>;
64
64
  export declare function routeFrame(ev: AgentEvent, ctx: FrameRouterCtx): Promise<FrameAction>;
65
+ export {};
@@ -39,6 +39,7 @@ export type FsApprovalOutcome = {
39
39
  retryExhausted?: true;
40
40
  currentPending?: GateCurrentPending | undefined;
41
41
  denyAttributionRefusal?: DenyAttributionRefusalDetail | undefined;
42
+ editRefused?: true;
42
43
  };
43
44
  export interface FsApprovalWireDeps {
44
45
  client: HitlClientLike;
@@ -140,15 +140,46 @@ function logAfterDecided(msg) {
140
140
  catch {
141
141
  }
142
142
  }
143
+ const ARGS_OMITTED_PATH_RECOVERED_NOTE = 'tool arguments exceeded the wire cap and were omitted — only the target path was recovered (from the approval question); ' +
144
+ 'the rest of the arguments, such as the content to write, is not shown, so you are deciding without seeing it. ' +
145
+ 'Edits are not accepted on this card: there is no original input to edit';
146
+ const ARGS_OMITTED_NOTHING_RECOVERED_NOTE = 'tool arguments exceeded the wire cap and were omitted, and nothing could be recovered from the approval question — ' +
147
+ 'you are deciding without seeing what this call will do; deny it if you are not sure. ' +
148
+ 'Edits are not accepted on this card: there is no original input to edit';
149
+ const PARK_ROW_ARGS_ABSENT_NOTE = "this request's tool arguments are not available on this surface — neither the event stream nor the pending approval record " +
150
+ 'carries them, so you are deciding without seeing them; deny it if you are not sure what it will do. ' +
151
+ 'Edits are not accepted on this card: there is no original input to edit';
152
+ const PARK_ROW_ARGS_TRUNCATED_NOTE = "this request's tool arguments were too large to keep on the pending approval record and were replaced by a size marker, " +
153
+ 'so you are deciding without seeing them; deny it if you are not sure what it will do. ' +
154
+ 'Edits are not accepted on this card: there is no original input to edit';
155
+ const PARK_EDIT_REFUSED_REASON = "the card returned an edited approval, but this request's tool arguments are not available on this surface " +
156
+ '(missing, or replaced by a size marker on the pending approval record), so the edit cannot be applied — nothing was sent; ' +
157
+ 'approve or deny it as it is';
158
+ function isParkRowInputSizeMarker(v) {
159
+ if (typeof v !== 'object' || v === null || Array.isArray(v))
160
+ return false;
161
+ const keys = Object.keys(v);
162
+ if (keys.length !== 2 || !keys.includes('truncated') || !keys.includes('bytes'))
163
+ return false;
164
+ const o = v;
165
+ return o.truncated === true && typeof o.bytes === 'number' && Number.isFinite(o.bytes) && o.bytes >= 0;
166
+ }
143
167
  export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signal, parkGatedCallId) {
144
168
  const found = await findPendingForTask(deps.client, taskId, (toolName) => toolName !== undefined && (toolNameIsFsWrite(toolName) || toolNameIsShellExec(toolName)), signal ? { signal } : undefined, parkGatedCallId, (row) => askParkRowArm(row) === 'tool-gate');
145
169
  if (!found.ok)
146
170
  return { kind: 'failed', stage: 'fetch', reason: found.reason, ...(found.code !== undefined ? { code: found.code } : {}) };
147
171
  const { pending, gatedCallId } = found;
148
172
  const toolName = typeof pending.toolName === 'string' ? pending.toolName : '';
149
- const args = (gatedCallId !== undefined ? argsByCall.get(gatedCallId) : undefined) ??
150
- pending.input ??
151
- {};
173
+ const streamArgs = gatedCallId !== undefined ? argsByCall.get(gatedCallId) : undefined;
174
+ const rowInput = pending.input;
175
+ const rowArgsGap = streamArgs !== undefined && streamArgs !== null
176
+ ? undefined
177
+ : rowInput === undefined || rowInput === null
178
+ ? 'absent'
179
+ : isParkRowInputSizeMarker(rowInput)
180
+ ? 'truncated'
181
+ : undefined;
182
+ const args = streamArgs !== undefined && streamArgs !== null ? streamArgs : rowArgsGap !== undefined ? {} : rowInput;
152
183
  if (typeof args !== 'object' || args === null) {
153
184
  return { kind: 'failed', stage: 'input', gatedCallId, reason: 'gate has no tool input payload' };
154
185
  }
@@ -165,8 +196,15 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
165
196
  args,
166
197
  callKey,
167
198
  ...(signal ? { signal } : {}),
199
+ ...(rowArgsGap !== undefined
200
+ ? {
201
+ argsUnavailable: true,
202
+ wireNote: rowArgsGap === 'truncated' ? PARK_ROW_ARGS_TRUNCATED_NOTE : PARK_ROW_ARGS_ABSENT_NOTE,
203
+ }
204
+ : {}),
168
205
  ...(pending.governanceForced === true ? { governanceForced: true } : {}),
169
206
  ...(pending.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
207
+ ...(pending.mandated === true ? { mandated: true } : {}),
170
208
  ...(pending.gateKind === 'irreversible_ask' ? { irreversibleParkGate: true } : {}),
171
209
  ...(pending.hasBidiControls === true ? { hasBidiControls: true } : {}),
172
210
  ...(ruleOffersReadOnly !== undefined ? { ruleOffersReadOnly } : {}),
@@ -184,6 +222,11 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
184
222
  ? { toolCallId: pending.toolCallId }
185
223
  : {}),
186
224
  });
225
+ if (rowArgsGap !== undefined && card.kind === 'allow' && card.updatedInput !== undefined) {
226
+ hostLog('error', `liveToolApprovalWire: approval card for ${gatedCallId ?? `task ${taskId}`} returned an edited approval, but the tool input is ${rowArgsGap === 'truncated' ? 'a size marker on the pending row' : 'missing from both the stream and the pending row'} — nothing sent (the park stays pending)`);
227
+ surfaceEditRefusedOnBlindAsk();
228
+ return { kind: 'failed', stage: 'card', gatedCallId, reason: PARK_EDIT_REFUSED_REASON, editRefused: true };
229
+ }
187
230
  const decideSpan = bridge.openDecideOperation();
188
231
  const decideOpts = { ...(signal ? { signal } : {}), operation: decideSpan };
189
232
  try {
@@ -733,10 +776,14 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
733
776
  ? frame.args
734
777
  : undefined;
735
778
  let wireNote;
779
+ let argsFallback = false;
736
780
  if (typeof args !== 'object' || args === null) {
781
+ argsFallback = true;
782
+ const p = pathFromGateMessage(frame.message);
783
+ args = p !== undefined ? { file_path: p } : {};
737
784
  if (frame.argsOmitted === true) {
738
- wireNote = 'tool arguments exceeded the wire cap and were omitted — the diff below is reconstructed from the gate message, not the full payload';
739
- hostLog('debug', `liveToolApprovalWire: frame ${approvalId} args omitted (>16KiB wire cap) — card falls back to message-derived path`);
785
+ wireNote = p !== undefined ? ARGS_OMITTED_PATH_RECOVERED_NOTE : ARGS_OMITTED_NOTHING_RECOVERED_NOTE;
786
+ hostLog('debug', `liveToolApprovalWire: frame ${approvalId} args omitted (>16KiB wire cap) — card falls back to ${p !== undefined ? 'the message-derived path' : 'an empty input (no path could be derived from the message)'}`);
740
787
  }
741
788
  if (lane?.argsUnavailable === true) {
742
789
  wireNote =
@@ -744,9 +791,8 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
744
791
  'so you are deciding without seeing them; deny it if you are not sure what it will do. ' +
745
792
  'Edits are not accepted on this card: there is no original input to edit';
746
793
  }
747
- const p = pathFromGateMessage(frame.message);
748
- args = p !== undefined ? { file_path: p } : {};
749
794
  }
795
+ const argsNotRealInput = argsFallback || lane?.argsUnavailable === true;
750
796
  const ruleOffers = readRuleOfferSupply(frame.ruleOffers, frame.ruleSuggestions);
751
797
  const denialLimitFallback = readDenialLimitFallback(frame.denialLimitFallback);
752
798
  const card = await surfaceApprovalCard({
@@ -759,7 +805,7 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
759
805
  ...(signal ? { signal } : {}),
760
806
  ...(isFromSubagent(frame) ? { workerBadge: subagentBadgeFor(frame) } : {}),
761
807
  ...(wireNote !== undefined ? { wireNote } : {}),
762
- ...(lane?.argsUnavailable === true ? { argsUnavailable: true } : {}),
808
+ ...(argsNotRealInput ? { argsUnavailable: true } : {}),
763
809
  ...(frame.governanceForced === true ? { governanceForced: true } : {}),
764
810
  ...(lane?.windowIsCurrent === true
765
811
  ? {
@@ -791,8 +837,8 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
791
837
  ...((k) => (k !== undefined ? { ruleStoreUnreadable: k } : {}))(readRuleStoreUnreadable(frame.ruleStoreUnreadable)),
792
838
  ...(typeof frame.origin === 'string' && frame.origin !== '' ? { origin: frame.origin } : {}),
793
839
  });
794
- if (lane?.argsUnavailable === true && card.kind === 'allow' && card.updatedInput !== undefined) {
795
- hostLog('error', `liveToolApprovalWire: ${approvalId} card returned an edited approval on an args-unavailable ask — nothing sent (the ask stays pending)`);
840
+ if (argsNotRealInput && card.kind === 'allow' && card.updatedInput !== undefined) {
841
+ hostLog('error', `liveToolApprovalWire: ${approvalId} card returned an edited approval on a card whose arguments are not the tool's real input (${lane?.argsUnavailable === true ? 'args-unavailable ask' : 'arguments omitted or absent on the frame'}) — nothing sent (the ask stays pending)`);
796
842
  surfaceEditRefusedOnBlindAsk();
797
843
  return { decision: 'unresolved', editRefused: true };
798
844
  }
@@ -12,6 +12,26 @@ function hooksLockedToPluginOnly(policyValue) {
12
12
  return policyValue.includes('hooks');
13
13
  return false;
14
14
  }
15
+ let warnedNoMergedReader = new WeakSet();
16
+ const NO_MERGED_READER_WARNING = 'client-core: the installed settings port does not implement mergedDisableAllHooks() — ' +
17
+ 'a disableAllHooks set in user, project, local or flag settings is not applied to hooks sent to the engine ' +
18
+ '(only a managed-settings disableAllHooks is). Implement SettingsPort.mergedDisableAllHooks() to return ' +
19
+ 'the effective merged value.';
20
+ function nonManagedDisableAllHooks(settings) {
21
+ try {
22
+ if (typeof settings.mergedDisableAllHooks === 'function')
23
+ return settings.mergedDisableAllHooks() === true;
24
+ }
25
+ catch (err) {
26
+ hostLog('debug', `hooksWireCaps: merged disableAllHooks read threw — fail-closed (only policySettings hooks projected to the engine): ${String(err)}`);
27
+ return true;
28
+ }
29
+ if (!warnedNoMergedReader.has(settings)) {
30
+ warnedNoMergedReader.add(settings);
31
+ hostLog('warn', NO_MERGED_READER_WARNING);
32
+ }
33
+ return false;
34
+ }
15
35
  function hooksOfSource(source) {
16
36
  try {
17
37
  const s = hostSettings()?.getSettingsForSource(source);
@@ -52,7 +72,12 @@ export function hooksForWire() {
52
72
  if (pluginOnlyHooks) {
53
73
  hostLog('debug', 'hooksWireCaps: strictPluginOnlyCustomization locks the hooks surface (managed) — only policySettings hooks projected to the engine');
54
74
  }
55
- const managedOnly = policy?.allowManagedHooksOnly === true || pluginOnlyHooks;
75
+ const managedByPolicy = policy?.allowManagedHooksOnly === true || pluginOnlyHooks;
76
+ const disabledByNonManaged = !managedByPolicy && nonManagedDisableAllHooks(settings);
77
+ if (disabledByNonManaged) {
78
+ hostLog('debug', 'hooksWireCaps: disableAllHooks (merged, non-managed) — only policySettings hooks projected to the engine');
79
+ }
80
+ const managedOnly = managedByPolicy || disabledByNonManaged;
56
81
  const sources = managedOnly
57
82
  ? ['policySettings']
58
83
  : ['policySettings', ...EDITABLE_HOOK_SOURCES];
@@ -75,6 +100,7 @@ export function hooksForWire() {
75
100
  }
76
101
  export function __resetHooksWireCapsForTests() {
77
102
  __resetGoalStopHookForTests();
103
+ warnedNoMergedReader = new WeakSet();
78
104
  }
79
105
  export function fleetHookFailureNoticeCapable(baseUrl) {
80
106
  return engineCapNestedTrue(baseUrl, 'fleet', 'hookFailureNotice');
package/dist/host.d.ts CHANGED
@@ -4,6 +4,7 @@ export type SettingsDocument = Record<string, unknown>;
4
4
  export interface SettingsPort {
5
5
  getSettingsForSource(source: string): SettingsDocument | null;
6
6
  shouldSkipHookDueToTrust(hook?: unknown): boolean;
7
+ mergedDisableAllHooks?(): boolean | undefined;
7
8
  }
8
9
  export interface FsPort {
9
10
  getScratchpadDir(): string | undefined;
@@ -15,7 +15,9 @@ export declare function probeScenarioTools(baseUrl: string, scenario: string, op
15
15
  tools: string[];
16
16
  toolset?: string;
17
17
  } | null>;
18
- export declare function resolveLiveInitToolFace(argv: readonly string[], env: EnvLike, opts?: {
18
+ export interface LiveInitToolFaceOptions {
19
19
  fetchImpl?: typeof fetch;
20
20
  timeoutMs?: number;
21
- }): Promise<LiveInitToolFace | null>;
21
+ webSearchStamped?: boolean;
22
+ }
23
+ export declare function resolveLiveInitToolFace(argv: readonly string[], env: EnvLike, opts?: LiveInitToolFaceOptions): Promise<LiveInitToolFace | null>;
@@ -2,7 +2,7 @@ import { makeEngineWireClient, resolveWireAuth } from './engineWireSdk.js';
2
2
  import { hostLog } from './host.js';
3
3
  import { scenarioForPrint } from './scenarioWire.js';
4
4
  import { resolveHeadlessInteractiveTools } from './interactiveToolsWire.js';
5
- import { webSearchFromEnv } from './webSearchWireCaps.js';
5
+ import { webSearchVerdictFromEnv } from './webSearchWireCaps.js';
6
6
  export const ENGINE_HANDS_BAND = [
7
7
  'Bash',
8
8
  'Read',
@@ -105,7 +105,7 @@ export async function resolveLiveInitToolFace(argv, env, opts) {
105
105
  }
106
106
  let webSearch = false;
107
107
  try {
108
- webSearch = webSearchFromEnv(env) !== undefined;
108
+ webSearch = typeof opts?.webSearchStamped === 'boolean' ? opts.webSearchStamped : webSearchVerdictFromEnv(env).kind === 'honored';
109
109
  }
110
110
  catch {
111
111
  }
@@ -1,5 +1,5 @@
1
1
  import { type EnvLike } from '../hostEnv.js';
2
- import { type ModelCatalogResult, type OnlineCatalogDoc } from './catalog.js';
2
+ import { type ModelCatalogResult } from './catalog.js';
3
3
  import type { ProviderPreset } from './providerPresets.js';
4
4
  export declare const DEFAULT_CATALOG_SOURCES: readonly string[];
5
5
  export declare const CATALOG_DEFAULT_HOSTS: readonly string[];
@@ -12,11 +12,6 @@ export interface CatalogSourceAttempt {
12
12
  shaChecked?: boolean;
13
13
  detail?: string;
14
14
  }
15
- export interface CatalogCacheEnvelope {
16
- fetchedAt?: number;
17
- sourceUrl: string;
18
- catalog: OnlineCatalogDoc;
19
- }
20
15
  export interface CatalogCachePort {
21
16
  read(path: string): string | null | Promise<string | null>;
22
17
  writeAtomic(path: string, text: string): void | Promise<void>;
@@ -1,6 +1,6 @@
1
1
  export declare function notePanelRowRunning(taskId: string): void;
2
2
  export declare function retirePanelRowRunning(taskId: string): void;
3
- export interface PanelRowEndedIdentity {
3
+ interface PanelRowEndedIdentity {
4
4
  cycleSeq?: number;
5
5
  startedAt?: number;
6
6
  }
@@ -11,7 +11,7 @@ export declare function holdNewbornTerminalRow(taskId: string, ev: object): void
11
11
  export declare function peekHeldNewbornTerminalRow<T extends object>(taskId: string): T | undefined;
12
12
  export declare function takeHeldNewbornTerminalRow<T extends object>(taskId: string): T | undefined;
13
13
  export declare function dropHeldNewbornTerminalRow(taskId: string): void;
14
- export interface HeldNewbornTerminalRef {
14
+ interface HeldNewbornTerminalRef {
15
15
  readonly taskId: string;
16
16
  readonly transcriptId?: string;
17
17
  readonly parentToolCallId?: string;
@@ -19,3 +19,4 @@ export interface HeldNewbornTerminalRef {
19
19
  export declare function heldNewbornTerminalRefs(): HeldNewbornTerminalRef[];
20
20
  export declare function __resetPanelRunningHistoryForTests(): void;
21
21
  export declare function __panelRunningHistorySize(): number;
22
+ export {};
@@ -1,7 +1,6 @@
1
1
  export declare const AGENT_MESSAGE_TAG = "agent-message";
2
2
  export declare const CROSS_SESSION_MESSAGE_TAG = "cross-session-message";
3
3
  export declare const PEER_FRAME_LANES: readonly ["agent_message", "cross_session_message", "cross_session_notice"];
4
- export type PeerFrameLane = (typeof PEER_FRAME_LANES)[number];
5
4
  export declare const CROSS_SESSION_NOTICE_KINDS: readonly ["delivery_notice", "idle_notice"];
6
5
  export type CrossSessionNoticeKind = (typeof CROSS_SESSION_NOTICE_KINDS)[number];
7
6
  export declare const PEER_MODE_CLASSES: readonly ["bypass", "prompting"];
@@ -19,7 +19,7 @@ export function printInitRosterSignal(message) {
19
19
  if (type === 'result')
20
20
  return { kind: 'no-roster', reason: 'run-ended' };
21
21
  if (type === 'assistant') {
22
- return rec.isApiErrorMessage === true ? { kind: 'no-roster', reason: 'run-ended' } : { kind: 'no-roster', reason: 'model-activity-first' };
22
+ return rec._sema_api_error_message === true ? { kind: 'no-roster', reason: 'run-ended' } : { kind: 'no-roster', reason: 'model-activity-first' };
23
23
  }
24
24
  if (type === 'stream_event' || type === 'retry_status')
25
25
  return { kind: 'no-roster', reason: 'model-activity-first' };
@@ -10,19 +10,19 @@ export const PRINT_NOTIFICATION_FIELD_MATRIX = [
10
10
  { field: 'tool_use_id', from: 'toolUseId', cc: true, why: 'CC 逐字 snake 名(wire 是 camel);把通知钉回发起它的那次工具调用' },
11
11
  { field: 'output_file', from: 'output_file', cc: true, why: 'CC 逐字;后台输出落盘路径' },
12
12
  { field: 'usage', from: 'usage', cc: true, why: 'CC 逐字 optional;计量面(交互面 XML 反而不读它 —— 那是渲染面口径)' },
13
- { field: 'stoppedBy', from: 'stoppedBy', cc: false, why: '🔴 残局归因核心位:谁停的(用户/上限/引擎)。修前 `-p` 出口零携带' },
14
- { field: 'resumable', from: 'resumable', cc: false, why: '🔴 残局归因:还能不能 resume —— 自动化要靠它决定重试还是放弃' },
15
- { field: 'partial', from: 'partial', cc: false, why: 'server 1.258:被终止子代的残果(消费方须渲成不完整,不得当完整结果)' },
16
- { field: 'exitCode', from: 'exitCode', cc: false, why: '后台 bash 退出码(137=OOM 这类归因只在这一位上)' },
17
- { field: 'diagnostics', from: 'diagnostics', cc: false, why: '引擎侧诊断串(UNTRUSTED,service 已 redact)' },
18
- { field: 'result', from: 'result', cc: false, why: '完成结果正文(#49 C1:帧带 result 必入 XML,print 出口同理不得独漏)' },
19
- { field: 'lines', from: 'lines', cc: false, why: '尾部输出行(UNTRUSTED);交互面折进 <output-lines>' },
20
- { field: 'recentSteps', from: 'recentSteps', cc: false, why: '结构化残局步骤 {tool,target,outcome}[] —— **原样透传**,不降级成交互面那份显示串(SDK 消费方要结构)' },
21
- { field: 'editedFiles', from: 'editedFiles', cc: false, why: '结构化改档清单 {path,edits}[];同上原样透传' },
22
- { field: 'task_type', from: 'task_type', cc: false, why: 'lane 位(external/background_agent/…)—— 去重键的分域依据,消费方要能自己算同一把键' },
23
- { field: 'source', from: 'source', cc: false, why: 'external lane 的来源标识(交互面渲成开标签 from= 属性)' },
24
- { field: 'seq', from: 'seq', cc: false, why: '停机周期/批次计数([508]②)—— 与 task_id+status 一起构成去重键' },
25
- { field: 'injected', from: 'injected', cc: false, why: '[1818]§四:本帧**不**证明 XML 已进模型上下文;消费方须按此位判,不得按帧到达判' },
13
+ { field: '_sema_stopped_by', from: 'stoppedBy', cc: false, why: '🔴 残局归因核心位:谁停的(用户/上限/引擎)。修前 `-p` 出口零携带' },
14
+ { field: '_sema_resumable', from: 'resumable', cc: false, why: '🔴 残局归因:还能不能 resume —— 自动化要靠它决定重试还是放弃' },
15
+ { field: '_sema_partial', from: 'partial', cc: false, why: 'server 1.258:被终止子代的残果(消费方须渲成不完整,不得当完整结果)' },
16
+ { field: '_sema_exit_code', from: 'exitCode', cc: false, why: '后台 bash 退出码(137=OOM 这类归因只在这一位上)' },
17
+ { field: '_sema_diagnostics', from: 'diagnostics', cc: false, why: '引擎侧诊断串(UNTRUSTED,service 已 redact)' },
18
+ { field: '_sema_result', from: 'result', cc: false, why: '完成结果正文(#49 C1:帧带 result 必入 XML,print 出口同理不得独漏)' },
19
+ { field: '_sema_lines', from: 'lines', cc: false, why: '尾部输出行(UNTRUSTED);交互面折进 <output-lines>' },
20
+ { field: '_sema_recent_steps', from: 'recentSteps', cc: false, why: '结构化残局步骤 {tool,target,outcome}[] —— **原样透传**,不降级成交互面那份显示串(SDK 消费方要结构)' },
21
+ { field: '_sema_edited_files', from: 'editedFiles', cc: false, why: '结构化改档清单 {path,edits}[];同上原样透传' },
22
+ { field: '_sema_task_type', from: 'task_type', cc: false, why: 'lane 位(external/background_agent/…)—— 去重键的分域依据,消费方要能自己算同一把键;CC 的 task_type 只在 task_started / task_progress 子型上且词表不同,故不占 CC 名' },
23
+ { field: '_sema_source', from: 'source', cc: false, why: 'external lane 的来源标识(交互面渲成开标签 from= 属性)' },
24
+ { field: '_sema_seq', from: 'seq', cc: false, why: '停机周期/批次计数([508]②)—— 与 task_id+status 一起构成去重键' },
25
+ { field: '_sema_injected', from: 'injected', cc: false, why: '[1818]§四:本帧**不**证明 XML 已进模型上下文;消费方须按此位判,不得按帧到达判' },
26
26
  ];
27
27
  const PRINT_FRAME_KEYS = new Set(PRINT_NOTIFICATION_FIELD_MATRIX.map(r => r.field));
28
28
  export function taskNotificationToPrintFrame(n, identity = {}) {
@@ -39,19 +39,19 @@ export function taskNotificationToPrintFrame(n, identity = {}) {
39
39
  ...(f.toolUseId !== undefined ? { tool_use_id: f.toolUseId } : {}),
40
40
  ...(f.outputFile !== undefined ? { output_file: f.outputFile } : {}),
41
41
  ...(n.usage !== undefined ? { usage: n.usage } : {}),
42
- ...(f.stoppedBy !== undefined ? { stoppedBy: f.stoppedBy } : {}),
43
- ...(f.resumable !== undefined ? { resumable: f.resumable } : {}),
44
- ...(f.partial !== undefined ? { partial: f.partial } : {}),
45
- ...(f.exitCode !== undefined ? { exitCode: f.exitCode } : {}),
46
- ...(f.diagnostics !== undefined ? { diagnostics: f.diagnostics } : {}),
47
- ...(f.result !== undefined ? { result: f.result } : {}),
48
- ...(f.lines !== undefined ? { lines: f.lines } : {}),
49
- ...(nonEmptyList(n.recentSteps) ? { recentSteps: n.recentSteps } : {}),
50
- ...(nonEmptyList(n.editedFiles) ? { editedFiles: n.editedFiles } : {}),
51
- ...(f.taskType !== undefined ? { task_type: f.taskType } : {}),
52
- ...(f.externalSource !== undefined ? { source: f.externalSource } : {}),
53
- ...(typeof n.seq === 'number' || typeof n.seq === 'string' ? { seq: n.seq } : {}),
54
- ...(typeof n.injected === 'boolean' ? { injected: n.injected } : {}),
42
+ ...(f.stoppedBy !== undefined ? { _sema_stopped_by: f.stoppedBy } : {}),
43
+ ...(f.resumable !== undefined ? { _sema_resumable: f.resumable } : {}),
44
+ ...(f.partial !== undefined ? { _sema_partial: f.partial } : {}),
45
+ ...(f.exitCode !== undefined ? { _sema_exit_code: f.exitCode } : {}),
46
+ ...(f.diagnostics !== undefined ? { _sema_diagnostics: f.diagnostics } : {}),
47
+ ...(f.result !== undefined ? { _sema_result: f.result } : {}),
48
+ ...(f.lines !== undefined ? { _sema_lines: f.lines } : {}),
49
+ ...(nonEmptyList(n.recentSteps) ? { _sema_recent_steps: n.recentSteps } : {}),
50
+ ...(nonEmptyList(n.editedFiles) ? { _sema_edited_files: n.editedFiles } : {}),
51
+ ...(f.taskType !== undefined ? { _sema_task_type: f.taskType } : {}),
52
+ ...(f.externalSource !== undefined ? { _sema_source: f.externalSource } : {}),
53
+ ...(typeof n.seq === 'number' || typeof n.seq === 'string' ? { _sema_seq: n.seq } : {}),
54
+ ...(typeof n.injected === 'boolean' ? { _sema_injected: n.injected } : {}),
55
55
  ...(identity.uuid !== undefined ? { uuid: identity.uuid } : {}),
56
56
  ...(identity.session_id !== undefined ? { session_id: identity.session_id } : {}),
57
57
  };
package/dist/seam.d.ts CHANGED
@@ -149,7 +149,7 @@ export type ChromeEvent = {
149
149
  } | ({
150
150
  kind: 'run_cost_reconciled';
151
151
  laneProof: LaneProof;
152
- } & RunCostReconcile) | HumanInputChromeEvent | MessageCommittedChromeEvent | EngineNoticeChromeEvent | TextSegmentEndChromeEvent | ThinkingSegmentEndChromeEvent | ToolDisclosureChromeEvent | ToolProgressChromeEvent | ToolRosterDeltaChromeEvent | ContextUsageChromeEvent | CompactionOutcomeChromeEvent | PromptAssembledChromeEvent | WiringManifestChromeEvent | ResultTextDivergedChromeEvent | ApprovalRequestChromeEvent | ApprovalRevokeChromeEvent;
152
+ } & RunCostReconcile) | HumanInputChromeEvent | MessageCommittedChromeEvent | EngineNoticeChromeEvent | TextSegmentEndChromeEvent | ThinkingSegmentEndChromeEvent | ToolDisclosureChromeEvent | ToolProgressChromeEvent | ToolRosterDeltaChromeEvent | ContextUsageChromeEvent | CompactionOutcomeChromeEvent | PromptAssembledChromeEvent | MemorySavedChromeEvent | WiringManifestChromeEvent | ResultTextDivergedChromeEvent | ApprovalRequestChromeEvent | ApprovalRevokeChromeEvent;
153
153
  export interface WiringManifestChromeEvent {
154
154
  kind: 'wiring_manifest';
155
155
  laneProof: LaneProof;
@@ -204,6 +204,12 @@ export interface CompactionOutcomeChromeEvent {
204
204
  reason?: string;
205
205
  eventId?: string;
206
206
  }
207
+ export interface MemorySavedChromeEvent {
208
+ kind: 'memory_saved';
209
+ laneProof: LaneProof;
210
+ id: string;
211
+ notes: string[];
212
+ }
207
213
  export interface PromptAssembledChromeEvent {
208
214
  kind: 'prompt_assembled';
209
215
  laneProof: LaneProof;
@@ -330,4 +336,4 @@ export declare function deriveTranscriptId(frame: {
330
336
  id?: unknown;
331
337
  seq?: unknown;
332
338
  toolCallId?: unknown;
333
- }, ctx: Pick<AdapterContext, 'uuid'>): string;
339
+ }, ctx: Pick<AdapterContext, 'uuid'>, suffix?: string): string;