@sema-agent/client-core 0.86.0 → 0.87.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.
@@ -1,6 +1,7 @@
1
1
  import { hostEnv } from './hostEnv.js';
2
2
  import { envFlagOff } from './envFlag.js';
3
3
  import { versionSupportsLimits } from './limitsWire.js';
4
+ import { resolveWireAuth } from './engineWireSdk.js';
4
5
  export const DETACH_HEADER = 'x-detach-on-disconnect';
5
6
  export const HEADLESS_DETACH_ENV = 'SEMA_HEADLESS_DETACH';
6
7
  export const DETACH_WIRE_MIN_ENGINE = [1, 196, 0];
@@ -80,6 +81,17 @@ let cancelArm = null;
80
81
  export function armDetachCancel(arm) {
81
82
  cancelArm = arm;
82
83
  }
84
+ function armAuthNow(baseUrl, source) {
85
+ let token;
86
+ try {
87
+ const v = source();
88
+ token = typeof v === 'string' ? v : undefined;
89
+ }
90
+ catch {
91
+ token = undefined;
92
+ }
93
+ return resolveWireAuth(baseUrl, token);
94
+ }
83
95
  export function isDetachArmed() {
84
96
  return cancelArm !== null;
85
97
  }
@@ -87,7 +99,16 @@ export function detachedTaskId() {
87
99
  return cancelArm?.getTaskId() ?? null;
88
100
  }
89
101
  export function detachCancelArm() {
90
- return cancelArm;
102
+ const arm = cancelArm;
103
+ if (arm === null)
104
+ return null;
105
+ const source = arm.authToken;
106
+ if (typeof source === 'function') {
107
+ const view = Object.defineProperties(Object.create(Object.getPrototypeOf(arm)), Object.getOwnPropertyDescriptors(arm));
108
+ Object.defineProperty(view, 'authToken', { value: armAuthNow(arm.baseUrl, source), enumerable: true, writable: true, configurable: true });
109
+ return view;
110
+ }
111
+ return arm;
91
112
  }
92
113
  export function _resetDetachWireForTest() {
93
114
  cancelArm = null;
@@ -14,6 +14,7 @@ export interface CrashConvergedRow {
14
14
  readonly decidedAtMs?: number;
15
15
  readonly convergedAtMs: number;
16
16
  readonly resumeSafe: boolean;
17
+ readonly needsHumanReason?: 'approved_then_interrupted' | 'pending_unsafe' | 'unstable_row' | 'contradictory';
17
18
  }
18
19
  export interface ApprovalsListEnvelope {
19
20
  pending: PendingCheckpoint[];
@@ -71,6 +71,15 @@ function readCrashConvergedRow(v) {
71
71
  return null;
72
72
  }
73
73
  }
74
+ function needsHumanReasonOf(reading) {
75
+ if (reading.unstable)
76
+ return 'unstable_row';
77
+ if (reading.orphanState === 'pending' ? reading.hasApprovalEvidence : reading.resumeSafe === true)
78
+ return 'contradictory';
79
+ if (reading.orphanState === 'decided')
80
+ return 'approved_then_interrupted';
81
+ return 'pending_unsafe';
82
+ }
74
83
  export function projectCrashConverged(env) {
75
84
  const resumeSafe = [];
76
85
  const needsHuman = [];
@@ -96,10 +105,16 @@ export function projectCrashConverged(env) {
96
105
  }
97
106
  const row = reading.row;
98
107
  const consistentlyUnexecuted = reading.orphanState === 'pending' && !reading.hasApprovalEvidence;
99
- if (!reading.unstable && consistentlyUnexecuted && reading.resumeSafe === true)
108
+ if (!reading.unstable && consistentlyUnexecuted && reading.resumeSafe === true) {
109
+ Reflect.deleteProperty(row, 'needsHumanReason');
100
110
  resumeSafe.push(row);
101
- else
111
+ }
112
+ else {
113
+ Object.defineProperty(row, 'needsHumanReason', {
114
+ value: needsHumanReasonOf(reading), enumerable: true, writable: true, configurable: true,
115
+ });
102
116
  needsHuman.push(row);
117
+ }
103
118
  }
104
119
  }
105
120
  catch {
@@ -47,6 +47,10 @@ export type PlanReviewModeAfter = (typeof PLAN_REVIEW_MODE_AFTER_WORDS)[number];
47
47
  export declare function isPlanReviewModeAfter(v: string | undefined): v is PlanReviewModeAfter;
48
48
  export type HitlSafetyCode = 'binding_mismatch' | 'no_pending' | 'wrong_gate' | 'bad_plan_edit' | 'bad_plan_mode' | 'empty_answer';
49
49
  export type HitlFailureStage = 'fetch' | 'input' | 'card' | 'decide' | 'orchestration';
50
+ export declare const NO_APPROVAL_CARD_PORT_REASON = "no approval card port installed (print/non-REPL mode, or host forgot installApprovalCardPort)";
51
+ export declare const APPROVAL_CARD_PORT_KEY_MISMATCH_REASON = "no approval card port installed for the default session key, but a non-default sessionKey has one \u2014 likely a keyed-install/default-read mismatch (installApprovalCardPortFor vs the zero-arg default-key read), not a missing install";
52
+ export declare const NO_QUESTION_OVERLAY_REASON = "no question overlay mounted (print/non-REPL mode)";
53
+ export declare function isNoDecisionSurfaceReason(reason: string): boolean;
50
54
  export interface HitlDecideDenyOutcome {
51
55
  decision: 'deny';
52
56
  reason?: string | undefined;
@@ -25,6 +25,12 @@ export const PLAN_REVIEW_MODE_AFTER_WORDS = Object.freeze(['default', 'acceptEdi
25
25
  export function isPlanReviewModeAfter(v) {
26
26
  return v !== undefined && PLAN_REVIEW_MODE_AFTER_WORDS.includes(v);
27
27
  }
28
+ export const NO_APPROVAL_CARD_PORT_REASON = 'no approval card port installed (print/non-REPL mode, or host forgot installApprovalCardPort)';
29
+ export const APPROVAL_CARD_PORT_KEY_MISMATCH_REASON = 'no approval card port installed for the default session key, but a non-default sessionKey has one — likely a keyed-install/default-read mismatch (installApprovalCardPortFor vs the zero-arg default-key read), not a missing install';
30
+ export const NO_QUESTION_OVERLAY_REASON = 'no question overlay mounted (print/non-REPL mode)';
31
+ export function isNoDecisionSurfaceReason(reason) {
32
+ return reason === NO_APPROVAL_CARD_PORT_REASON || reason === APPROVAL_CARD_PORT_KEY_MISMATCH_REASON || reason === NO_QUESTION_OVERLAY_REASON;
33
+ }
28
34
  export class HitlSafetyError extends Error {
29
35
  code;
30
36
  constructor(message, code) {
@@ -1,5 +1,5 @@
1
1
  import { putOwnKey } from '../ownKey.js';
2
- import { DecideTransportRetryExhaustedError, HitlBridge, HitlSafetyError, findPendingForTask, readDecideCurrentPending, readWireErrorCode, } from './hitlBridge.js';
2
+ import { DecideTransportRetryExhaustedError, HitlBridge, HitlSafetyError, NO_QUESTION_OVERLAY_REASON, findPendingForTask, isNoDecisionSurfaceReason, readDecideCurrentPending, readWireErrorCode, } from './hitlBridge.js';
3
3
  import { PARKED_RESUME_STARTUP_FAILED, RESUME_POLICY_FOLD_CODE, isDecideParkMovedCode } from '../engineErrorCodes.js';
4
4
  import { publishQuestionFrame, registerLocalQuestionResponder, hasQuestionOverlay, } from '../liveQuestionStore.js';
5
5
  import { hostLog } from '../host.js';
@@ -75,7 +75,7 @@ export function toAnsweredOutput(questions, answer) {
75
75
  }
76
76
  async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGatedCallId, onPresented) {
77
77
  if (!hasQuestionOverlay()) {
78
- return { kind: 'failed', stage: 'card', reason: 'no question overlay mounted (print/non-REPL mode)' };
78
+ return { kind: 'failed', stage: 'card', reason: NO_QUESTION_OVERLAY_REASON };
79
79
  }
80
80
  const found = await findPendingForTask(deps.client, taskId, isAskTool, signal ? { signal } : undefined, parkGatedCallId, (row) => isAskTool(typeof row.toolName === 'string' ? row.toolName : undefined));
81
81
  if (!found.ok)
@@ -301,7 +301,8 @@ export async function resolvePark(park, ctx) {
301
301
  : outcome.kind === 'retracted'
302
302
  ? 'the approval card was retracted by the host without a decision (nothing was sent; the run stays parked and remains decidable elsewhere)'
303
303
  : undefined;
304
- return { kind: 'failsoft', events: failsoftEvents(park, ctx, outcome.kind === 'aborted', failedReason) };
304
+ const noDecisionSurface = outcome.kind === 'failed' && outcome.stage === 'card' && isNoDecisionSurfaceReason(outcome.reason);
305
+ return { kind: 'failsoft', events: failsoftEvents(park, ctx, outcome.kind === 'aborted', failedReason, undefined, noDecisionSurface) };
305
306
  }
306
307
  led.dropHeldForDecidedPark(outcome.gatedCallId, park.gatedCallId);
307
308
  if (outcome.gatedCallId) {
@@ -324,7 +325,15 @@ export async function resolvePark(park, ctx) {
324
325
  ...(handoffTaskId !== undefined ? { handoffTaskId } : {}),
325
326
  };
326
327
  }
327
- function failsoftEvents(park, ctx, gateAbortedByUser, failedReason, terminal) {
328
+ function failsoftReasonSentence(park, failedReason, terminal) {
329
+ if (terminal !== undefined)
330
+ return terminal.errorMessage;
331
+ if (failedReason === undefined)
332
+ return undefined;
333
+ return (`${park.gate === 'fs' ? 'Tool approval' : 'AskUserQuestion'} gate could not be answered: ${failedReason}` +
334
+ ' — the run is still parked on this approval: decide it on the card when it is shown again, or cancel the run.');
335
+ }
336
+ function failsoftEvents(park, ctx, gateAbortedByUser, failedReason, terminal, noDecisionSurface = false) {
328
337
  const events = [
329
338
  ...flushHeldWithInterruptRewrite(ctx.led, {
330
339
  terminal: park.pendingDone,
@@ -332,19 +341,15 @@ function failsoftEvents(park, ctx, gateAbortedByUser, failedReason, terminal) {
332
341
  gateAbortedByUser,
333
342
  }),
334
343
  ];
344
+ const reason = failsoftReasonSentence(park, failedReason, terminal);
335
345
  if (park.pendingDone) {
336
- events.push(park.pendingDone);
346
+ events.push(reason === undefined || noDecisionSurface ? park.pendingDone : { ...park.pendingDone, _sema_park_failed_reason: reason });
337
347
  }
338
348
  else if (terminal !== undefined) {
339
349
  events.push({ type: 'failed', ...terminal });
340
350
  }
341
- else if (failedReason !== undefined) {
342
- events.push({
343
- type: 'failed',
344
- errorCode: 'hitl_unanswered',
345
- errorMessage: `${park.gate === 'fs' ? 'Tool approval' : 'AskUserQuestion'} gate could not be answered: ${failedReason}` +
346
- ' — the run is still parked on this approval: decide it on the card when it is shown again, or cancel the run.',
347
- });
351
+ else if (reason !== undefined) {
352
+ events.push({ type: 'failed', errorCode: 'hitl_unanswered', errorMessage: reason });
348
353
  }
349
354
  return events;
350
355
  }
@@ -7,3 +7,10 @@ export type SuspendedReopenReading = {
7
7
  readonly kind: 'unstated';
8
8
  };
9
9
  export declare function suspendedReopenOf(ev: unknown): SuspendedReopenReading;
10
+ export interface ResumeReopenDetail {
11
+ code: string;
12
+ reason: 'env_failed' | 'tool_unavailable' | 'tool_contract_mismatch';
13
+ retriable: true;
14
+ }
15
+ export declare function resumeReopenFromError(err: unknown): ResumeReopenDetail | null;
16
+ export declare function resumeReopenContent(detail: ResumeReopenDetail): string;
@@ -1,13 +1,44 @@
1
+ import { RESUME_REOPEN_CODES, RESUME_ENV_FAILED, RESUME_TOOL_UNAVAILABLE } from '../engineErrorCodes.js';
1
2
  const UNSTATED = Object.freeze({ kind: 'unstated' });
2
3
  const NOT_REOPENED = Object.freeze({ kind: 'not_reopened' });
3
4
  export function suspendedReopenOf(ev) {
4
- if (ev === null || typeof ev !== 'object' || Array.isArray(ev))
5
+ if (ev === null || typeof ev !== 'object')
5
6
  return UNSTATED;
6
- const o = ev;
7
- if (!Object.hasOwn(o, 'reopened'))
7
+ try {
8
+ if (Array.isArray(ev))
9
+ return UNSTATED;
10
+ const o = ev;
11
+ if (!Object.hasOwn(o, 'reopened'))
12
+ return UNSTATED;
13
+ const v = o.reopened;
14
+ if (v === null)
15
+ return NOT_REOPENED;
16
+ return typeof v === 'string' && v.length > 0 ? { kind: 'reopened', code: v } : UNSTATED;
17
+ }
18
+ catch {
8
19
  return UNSTATED;
9
- const v = o.reopened;
10
- if (v === null)
11
- return NOT_REOPENED;
12
- return typeof v === 'string' && v.length > 0 ? { kind: 'reopened', code: v } : UNSTATED;
20
+ }
21
+ }
22
+ export function resumeReopenFromError(err) {
23
+ if (typeof err !== 'object' || err === null)
24
+ return null;
25
+ const code = err.errorCode;
26
+ if (typeof code !== 'string' || !RESUME_REOPEN_CODES.includes(code))
27
+ return null;
28
+ if (err.retriable !== true)
29
+ return null;
30
+ const reason = code.slice('resume.'.length);
31
+ return { code, reason, retriable: true };
32
+ }
33
+ export function resumeReopenContent(detail) {
34
+ const first = detail.code === RESUME_ENV_FAILED
35
+ ? 'The execution environment for this parked work could not be brought back'
36
+ : detail.code === RESUME_TOOL_UNAVAILABLE
37
+ ? 'The tool this approval was granted for is not available on this engine'
38
+ : 'The tool this approval was granted for has changed since the approval was recorded';
39
+ return [
40
+ first,
41
+ 'The engine re-parked the work before consuming anything — the approval is still pending and your decision was not consumed',
42
+ 'Re-fetch the pending approvals and decide again on an engine where the same tool is available',
43
+ ].join(' · ');
13
44
  }
@@ -1,4 +1,4 @@
1
- import { DEFAULT_DENY_REASON, DecideTransportRetryExhaustedError, HitlBridge, HitlSafetyError, denyReasonForWire, findPendingForTask, readDecideCurrentPending, readWireErrorCode, } from './hitlBridge.js';
1
+ import { APPROVAL_CARD_PORT_KEY_MISMATCH_REASON, DEFAULT_DENY_REASON, DecideTransportRetryExhaustedError, HitlBridge, NO_APPROVAL_CARD_PORT_REASON, HitlSafetyError, denyReasonForWire, findPendingForTask, readDecideCurrentPending, readWireErrorCode, } from './hitlBridge.js';
2
2
  import { askParkRowArm } from './askParkRowRouting.js';
3
3
  import { hostLog } from '../host.js';
4
4
  import { readOwnNonEmptyString } from '../ownKey.js';
@@ -120,10 +120,9 @@ function cardPortMissReason() {
120
120
  const otherKeysInstalled = cardPortByKey
121
121
  .keys()
122
122
  .some((k) => k !== DEFAULT_SESSION_KEY && cardPortByKey.get(k) != null);
123
- if (otherKeysInstalled) {
124
- return 'no approval card port installed for the default session key, but a non-default sessionKey has one — likely a keyed-install/default-read mismatch (installApprovalCardPortFor vs the zero-arg default-key read), not a missing install';
125
- }
126
- return 'no approval card port installed (print/non-REPL mode, or host forgot installApprovalCardPort)';
123
+ if (otherKeysInstalled)
124
+ return APPROVAL_CARD_PORT_KEY_MISMATCH_REASON;
125
+ return NO_APPROVAL_CARD_PORT_REASON;
127
126
  }
128
127
  function rememberRefusedByServer(e) {
129
128
  switch (readWireErrorCode(e)) {
package/dist/index.d.ts CHANGED
@@ -145,6 +145,7 @@ export * from './principalWire.js';
145
145
  export * from './wireErrorTriage.js';
146
146
  export * from './corruptSession.js';
147
147
  export * from './corruptSessionCopy.js';
148
+ export { classifyToolHistoryMismatch, resolveToolHistoryRewindTarget } from './toolHistoryMismatch.js';
148
149
  export * from './resumeRefusalCopy.js';
149
150
  export * from './wireRefusalCopy.js';
150
151
  export * from './inheritEnvWire.js';
package/dist/index.js CHANGED
@@ -133,6 +133,7 @@ export * from './principalWire.js';
133
133
  export * from './wireErrorTriage.js';
134
134
  export * from './corruptSession.js';
135
135
  export * from './corruptSessionCopy.js';
136
+ export { classifyToolHistoryMismatch, resolveToolHistoryRewindTarget } from './toolHistoryMismatch.js';
136
137
  export * from './resumeRefusalCopy.js';
137
138
  export * from './wireRefusalCopy.js';
138
139
  export * from './inheritEnvWire.js';
@@ -19,13 +19,8 @@ export interface ResumePolicyBlockDetail {
19
19
  }
20
20
  export declare function resumePolicyBlockFromError(err: unknown): ResumePolicyBlockDetail | null;
21
21
  export declare function resumePolicyBlockContent(detail: ResumePolicyBlockDetail): string;
22
- export interface ResumeReopenDetail {
23
- code: string;
24
- reason: 'env_failed' | 'tool_unavailable' | 'tool_contract_mismatch';
25
- retriable: true;
26
- }
27
- export declare function resumeReopenFromError(err: unknown): ResumeReopenDetail | null;
28
- export declare function resumeReopenContent(detail: ResumeReopenDetail): string;
22
+ export { resumeReopenFromError, resumeReopenContent } from './hitl/suspendedReopen.js';
23
+ export type { ResumeReopenDetail } from './hitl/suspendedReopen.js';
29
24
  export interface ResumeContextUnavailableDetail {
30
25
  code: typeof RESUME_CONTEXT_UNAVAILABLE;
31
26
  staleAfterSec?: number;
@@ -1,5 +1,5 @@
1
1
  import { readErrorNumber, readErrorString } from './wireFailureShape.js';
2
- import { RESUME_CONTEXT_UNAVAILABLE, RESUME_PLACEMENT_MISMATCH, RESUME_PREFLIGHT_REJECTED, RESUME_REOPEN_CODES, RESUME_ENV_FAILED, RESUME_TOOL_UNAVAILABLE, RESUME_POLICY_FOLD_CODE, PARKED_RESUME_STARTUP_FAILED, isStatusDerivedCoarseCode, } from './engineErrorCodes.js';
2
+ import { RESUME_CONTEXT_UNAVAILABLE, RESUME_PLACEMENT_MISMATCH, RESUME_PREFLIGHT_REJECTED, RESUME_POLICY_FOLD_CODE, PARKED_RESUME_STARTUP_FAILED, isStatusDerivedCoarseCode, } from './engineErrorCodes.js';
3
3
  import { capForDisplay } from './fleetTaskDesc.js';
4
4
  import { resumeRetryLaterFromError } from './wireErrorTriage.js';
5
5
  export const RESUME_REFUSAL_CODES = Object.freeze([
@@ -85,29 +85,7 @@ export function resumePolicyBlockContent(detail) {
85
85
  "This refusal does not say whether anything was consumed before it or whether a retry would pass — check the task's current state before deciding again, and ask whoever operates this deployment if it keeps happening",
86
86
  ].join(' · ');
87
87
  }
88
- export function resumeReopenFromError(err) {
89
- if (typeof err !== 'object' || err === null)
90
- return null;
91
- const code = err.errorCode;
92
- if (typeof code !== 'string' || !RESUME_REOPEN_CODES.includes(code))
93
- return null;
94
- if (err.retriable !== true)
95
- return null;
96
- const reason = code.slice('resume.'.length);
97
- return { code, reason, retriable: true };
98
- }
99
- export function resumeReopenContent(detail) {
100
- const first = detail.code === RESUME_ENV_FAILED
101
- ? 'The execution environment for this parked work could not be brought back'
102
- : detail.code === RESUME_TOOL_UNAVAILABLE
103
- ? 'The tool this approval was granted for is not available on this engine'
104
- : 'The tool this approval was granted for has changed since the approval was recorded';
105
- return [
106
- first,
107
- 'The engine re-parked the work before consuming anything — the approval is still pending and your decision was not consumed',
108
- 'Re-fetch the pending approvals and decide again on an engine where the same tool is available',
109
- ].join(' · ');
110
- }
88
+ export { resumeReopenFromError, resumeReopenContent } from './hitl/suspendedReopen.js';
111
89
  export function resumeContextUnavailableFromError(err) {
112
90
  if (typeof err !== 'object' || err === null)
113
91
  return null;
@@ -1,5 +1,6 @@
1
1
  import { engineWireTarget } from './engineWireTarget.js';
2
2
  import { createEngineCapReader } from './engineCapReader.js';
3
+ import { _wireConversationRewindJudge } from './toolHistoryMismatch.js';
3
4
  const CAPS_KEYS = {
4
5
  conversationAnchor: 'resumeAt',
5
6
  forkRestore: 'rewindFiles',
@@ -37,8 +38,12 @@ const reader = createEngineCapReader({
37
38
  makeUnobserved: () => ({ kind: 'unobserved' }),
38
39
  });
39
40
  export function noteEngineCapsForRewindArchive(baseUrl, caps, opts) {
41
+ _wireConversationRewindJudge(conversationRewindJudge);
40
42
  reader.note(baseUrl, caps, opts);
41
43
  }
44
+ function conversationRewindJudge(engineBaseUrl) {
45
+ return rewindConversationAvailability(observedRewindArchive(engineBaseUrl));
46
+ }
42
47
  export function observedRewindArchive(baseUrl = engineWireTarget()?.baseUrl) {
43
48
  return reader.observed(baseUrl);
44
49
  }
package/dist/seam.d.ts CHANGED
@@ -3,6 +3,7 @@ import type { EngineTurnUsage } from './adapter/downstream/turnUsageToModelUsage
3
3
  import type { RunCostReconcile } from './adapter/downstream/terminalToSdkResult.js';
4
4
  import type { WiringManifestAutoMode, WiringManifestModelGate, WiringManifestMcpEntryView, WiringManifestHookEntryView, WiringManifestLspSeamView, WiringManifestWriteProtectionView, WiringManifestAutoConsolidationView, WiringManifestReadDenyView, ContextUsageSectionView, PromptManifestSectionView, PromptManifestBlockView, PromptManifestToolView } from './adapter/downstream/eventToSdkMessage.js';
5
5
  import type { ToolRosterView, ToolRosterDeltaView, WiringManifestHandsView } from './toolRoster.js';
6
+ import type { SuspendedReopenReading } from './hitl/suspendedReopen.js';
6
7
  export interface AbortSignalLike {
7
8
  readonly aborted: boolean;
8
9
  }
@@ -149,7 +150,16 @@ export type ChromeEvent = {
149
150
  } | ({
150
151
  kind: 'run_cost_reconciled';
151
152
  laneProof: LaneProof;
152
- } & RunCostReconcile) | HumanInputChromeEvent | MessageCommittedChromeEvent | EngineNoticeChromeEvent | TextSegmentEndChromeEvent | ThinkingSegmentEndChromeEvent | ToolDisclosureChromeEvent | ToolProgressChromeEvent | ToolRosterDeltaChromeEvent | ContextUsageChromeEvent | CompactionOutcomeChromeEvent | PromptAssembledChromeEvent | MemorySavedChromeEvent | WiringManifestChromeEvent | ResultTextDivergedChromeEvent | ApprovalRequestChromeEvent | ApprovalRevokeChromeEvent;
153
+ } & RunCostReconcile) | HumanInputChromeEvent | MessageCommittedChromeEvent | EngineNoticeChromeEvent | TextSegmentEndChromeEvent | ThinkingSegmentEndChromeEvent | ToolDisclosureChromeEvent | ToolProgressChromeEvent | ToolRosterDeltaChromeEvent | ContextUsageChromeEvent | CompactionOutcomeChromeEvent | PromptAssembledChromeEvent | MemorySavedChromeEvent | WiringManifestChromeEvent | ResultTextDivergedChromeEvent | ApprovalRequestChromeEvent | ApprovalRevokeChromeEvent | {
154
+ kind: 'suspended_reopened';
155
+ laneProof: LaneProof;
156
+ reopen: Extract<SuspendedReopenReading, {
157
+ kind: 'reopened';
158
+ }>;
159
+ content?: string;
160
+ eventId?: string;
161
+ taskId?: string;
162
+ };
153
163
  export interface WiringManifestChromeEvent {
154
164
  kind: 'wiring_manifest';
155
165
  laneProof: LaneProof;
package/dist/seam.js CHANGED
@@ -126,6 +126,13 @@ const CHROME_ARM_TABLE = {
126
126
  '🔴 账本重放的历史帧只用于时间线渲染,卡集的全量对账基准是开流 preamble(它带的 expiresInMs 是铸帧时刻的旧值);' +
127
127
  'card 的 message/args 是模型/工具产文,只渲染永不回喂模型',
128
128
  },
129
+ suspended_reopened: {
130
+ required: false,
131
+ duty: '可选:引擎确认把一次已送出的决定重新挂回待决 ⇒ 上屏一句「决定没被消费、再决一次」(`content`,单源句;缺席 = 码不在 reopen 族闭集,可照 `reopen.code` 原词显示,开集按成员渲染必带 default 臂)。' +
132
+ '🔴 按 `eventId` 幂等(同一条流的重放本包已去重;前台腿与接管腿各发一条、同 eventId,去重归宿主);' +
133
+ '🔴 接管 / 从头重放的腿:本臂之后同流又来「门已不在」的帧(text / tool_start / turn_end / done / failed 或新的挂起)⇒ 那次重开是历史,别当现状上屏;会话已切走的别补发 —— 这两件归宿主判;' +
134
+ '明说没重开 / 老引擎缺键一条都不发,本臂没来 ≠ 确认没重开;`taskId` 只在帧上自带时在场',
135
+ },
129
136
  approval_revoke: {
130
137
  required: true,
131
138
  duty: '用 sdk isApprovalRevokeFrameV1 窄化 frame 取 batchId/askIds/taskId;窄不下来按通用撤卡处理(该批本地卡照清);' +
@@ -0,0 +1,63 @@
1
+ export declare const TOOL_HISTORY_MISMATCH_KINDS: readonly ["tool_use_without_result", "orphan_tool_result", "duplicate_tool_use_id"];
2
+ export type ToolHistoryMismatchKind = (typeof TOOL_HISTORY_MISMATCH_KINDS)[number];
3
+ export interface ToolHistoryMismatch {
4
+ kind: ToolHistoryMismatchKind;
5
+ toolUseId?: string;
6
+ }
7
+ export interface ToolHistoryErrorCarrier {
8
+ readonly code?: string | null | undefined;
9
+ readonly status?: number | null | undefined;
10
+ readonly message?: string | null | undefined;
11
+ }
12
+ export interface ToolHistoryMismatchAnchor {
13
+ readonly kind: ToolHistoryMismatchKind;
14
+ readonly template: RegExp;
15
+ readonly idAfter: RegExp | null;
16
+ }
17
+ export declare const TOOL_HISTORY_MISMATCH_ANCHORS: readonly ToolHistoryMismatchAnchor[];
18
+ export declare function classifyToolHistoryMismatch(err: ToolHistoryErrorCarrier | null | undefined): ToolHistoryMismatch | null;
19
+ export declare const TOOL_HISTORY_REWIND_SENTENCE = "Run /rewind to recover the conversation.";
20
+ export declare function toolHistoryRepeatSentence(n: number): string;
21
+ type ConversationRewindVerdict = 'available' | 'unavailable' | 'unknown';
22
+ export declare function _wireConversationRewindJudge(judge: (engineBaseUrl: string) => ConversationRewindVerdict): void;
23
+ export declare function toolHistoryRewindOffered(declared: boolean | {
24
+ readonly engineBaseUrl: string;
25
+ } | undefined): boolean;
26
+ export interface ToolHistoryMismatchRowKey {
27
+ kind: ToolHistoryMismatchKind;
28
+ toolUseId?: string;
29
+ consecutive?: number;
30
+ }
31
+ export declare function toolHistoryMismatchRowKey(m: ToolHistoryMismatch, consecutive: number | undefined): ToolHistoryMismatchRowKey;
32
+ export declare function readToolHistoryMismatchKey(v: object | null | undefined): ToolHistoryMismatchRowKey | undefined;
33
+ export declare const TOOL_HISTORY_LEDGER_CAP = 64;
34
+ export declare function openToolHistoryGeneration(): number;
35
+ export interface ToolHistoryTerminalNote {
36
+ sessionId: string | undefined;
37
+ sessionEchoed: boolean;
38
+ generation: number;
39
+ runId: string | undefined;
40
+ replay: boolean;
41
+ mismatch: ToolHistoryMismatch | null;
42
+ }
43
+ export declare function noteToolHistoryTerminal(note: ToolHistoryTerminalNote): number | undefined;
44
+ export declare function _resetToolHistoryLedgerForTest(): void;
45
+ export interface ToolHistoryRow {
46
+ readonly toolUseIds?: readonly string[];
47
+ readonly toolResultIds?: readonly string[];
48
+ readonly prompt?: boolean;
49
+ readonly engineHandle?: string;
50
+ readonly local?: boolean;
51
+ readonly sessionRoot?: boolean;
52
+ }
53
+ export declare const TOOL_HISTORY_UNDECIDABLE_REASONS: readonly ["no-mismatch", "no-tool-use-id", "id-not-found", "prompt-not-found", "prompt-without-handle", "unreadable-rows"];
54
+ export type ToolHistoryUndecidableReason = (typeof TOOL_HISTORY_UNDECIDABLE_REASONS)[number];
55
+ export type ToolHistoryRewindResolution<R> = {
56
+ target: R;
57
+ } | {
58
+ rootReopen: true;
59
+ } | {
60
+ undecidable: ToolHistoryUndecidableReason;
61
+ };
62
+ export declare function resolveToolHistoryRewindTarget<R extends ToolHistoryRow>(rows: readonly (R | null | undefined)[] | null | undefined, mismatch: ToolHistoryMismatch | null | undefined): ToolHistoryRewindResolution<R>;
63
+ export {};