@sema-agent/client-core 0.104.0 → 0.105.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 (92) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/README.md +13 -5
  3. package/dist/adapt/arms.js +14 -6
  4. package/dist/adapter/activeRunSelfHeal.d.ts +1 -0
  5. package/dist/adapter/activeRunSelfHeal.js +10 -5
  6. package/dist/adapter/downstream/eventToSdkMessage.js +4 -4
  7. package/dist/adapter/downstream/wiringManifestView.d.ts +6 -0
  8. package/dist/adapter/downstream/wiringManifestView.js +25 -0
  9. package/dist/adapter/runStream.js +5 -1
  10. package/dist/cloudConfigWireCaps.js +3 -0
  11. package/dist/compensations.js +6 -6
  12. package/dist/coreValuePorts.d.ts +3 -1
  13. package/dist/coreValuePorts.js +4 -0
  14. package/dist/displayUntrusted.d.ts +4 -2
  15. package/dist/displayUntrusted.js +45 -22
  16. package/dist/engineGenerationPort.d.ts +11 -2
  17. package/dist/engineGenerationPort.js +63 -9
  18. package/dist/engineHealthFacts.d.ts +5 -0
  19. package/dist/engineHealthFacts.js +11 -0
  20. package/dist/eventLeg.d.ts +1 -1
  21. package/dist/eventLeg.js +23 -3
  22. package/dist/generated/engineFactTables.d.ts +1 -0
  23. package/dist/generated/engineFactTables.js +1 -0
  24. package/dist/generated/sdkFactTables.d.ts +2 -0
  25. package/dist/generated/sdkFactTables.js +9 -0
  26. package/dist/headlessReconnectWire.js +31 -4
  27. package/dist/hitl/askGateWire.js +26 -2
  28. package/dist/hitl/frameRouter.d.ts +4 -0
  29. package/dist/hitl/frameRouter.js +14 -0
  30. package/dist/hitl/gateIdentity.d.ts +1 -0
  31. package/dist/hitl/gateIdentity.js +3 -0
  32. package/dist/hitl/livePendingAsk.d.ts +2 -1
  33. package/dist/hitl/livePendingAsk.js +7 -1
  34. package/dist/hitl/planReviewCard.d.ts +21 -0
  35. package/dist/hitl/planReviewCard.js +229 -0
  36. package/dist/hitl/planReviewWire.d.ts +38 -13
  37. package/dist/hitl/planReviewWire.js +355 -133
  38. package/dist/hitl/toolApprovalWire.d.ts +2 -2
  39. package/dist/hitl/toolApprovalWire.js +53 -32
  40. package/dist/hostSettingsSource.d.ts +1 -1
  41. package/dist/hostSettingsSource.js +8 -4
  42. package/dist/hostTurnSettings.d.ts +1 -1
  43. package/dist/hostTurnSettings.js +29 -10
  44. package/dist/i18n/messages.en.d.ts +50 -0
  45. package/dist/i18n/messages.en.js +50 -0
  46. package/dist/i18n/messages.zh.js +51 -0
  47. package/dist/index.d.ts +15 -3
  48. package/dist/index.js +9 -1
  49. package/dist/mcpTransportKindsCapability.d.ts +18 -0
  50. package/dist/mcpTransportKindsCapability.js +75 -0
  51. package/dist/mcpWireCaps.d.ts +4 -1
  52. package/dist/mcpWireCaps.js +24 -13
  53. package/dist/notifications.d.ts +9 -0
  54. package/dist/notifications.js +11 -2
  55. package/dist/ownKey.d.ts +4 -0
  56. package/dist/ownKey.js +8 -0
  57. package/dist/permissionModeWriteCapability.d.ts +13 -0
  58. package/dist/permissionModeWriteCapability.js +35 -0
  59. package/dist/permissionsAllowGrantCapability.d.ts +3 -0
  60. package/dist/permissionsAllowGrantCapability.js +26 -3
  61. package/dist/permissionsDeclaration.d.ts +23 -1
  62. package/dist/permissionsDeclaration.js +60 -30
  63. package/dist/planReviewCardFacts.d.ts +116 -0
  64. package/dist/planReviewCardFacts.js +469 -0
  65. package/dist/reattachWindow.d.ts +8 -1
  66. package/dist/reattachWindow.js +44 -12
  67. package/dist/runFollow.d.ts +30 -0
  68. package/dist/runFollow.js +117 -0
  69. package/dist/runLedgerSnapshot.d.ts +20 -0
  70. package/dist/runLedgerSnapshot.js +272 -0
  71. package/dist/runPermissionModeSwitch.d.ts +57 -0
  72. package/dist/runPermissionModeSwitch.js +135 -0
  73. package/dist/runReattachPolicy.d.ts +13 -5
  74. package/dist/runReattachPolicy.js +58 -23
  75. package/dist/seam.d.ts +3 -0
  76. package/dist/seam.js +1 -1
  77. package/dist/seatContract.js +1 -0
  78. package/dist/sessionMemoryErase.d.ts +1 -1
  79. package/dist/sessionMemoryErase.js +5 -0
  80. package/dist/sessionMemoryStatus.d.ts +27 -1
  81. package/dist/sessionMemoryStatus.js +117 -0
  82. package/dist/sseIdleTriage.d.ts +3 -1
  83. package/dist/sseIdleTriage.js +19 -4
  84. package/dist/steering.d.ts +1 -0
  85. package/dist/steering.js +34 -26
  86. package/dist/subagent/engineSubagentTail.js +2 -0
  87. package/dist/subagentContentStore.d.ts +3 -0
  88. package/dist/subagentContentStore.js +10 -1
  89. package/dist/turnFailurePresentation.d.ts +5 -2
  90. package/dist/turnFailurePresentation.js +12 -4
  91. package/docs/INTEGRATION-CLIENTS.md +587 -10
  92. package/package.json +2 -2
@@ -1,46 +1,81 @@
1
1
  import { STREAM_DRAINING_RECONNECT_MAX, drainingRetryDelayMs, isPreStreamDrainingReject } from './wireErrorTriage.js';
2
2
  import { drainingLegStep } from './streamDrainingLegs.js';
3
+ import { mintRunNotFollowed } from './runFollow.js';
3
4
  export const REATTACH_MAX_TRIES = 5;
4
5
  const DRAINING_TOTAL_MAX = REATTACH_MAX_TRIES * STREAM_DRAINING_RECONNECT_MAX;
5
6
  export const REATTACH_BUDGET_FRESH = Object.freeze({ tries: 0, drainingStreak: 0, drainingTotal: 0 });
7
+ export const REATTACH_BUDGET_NONE = Object.freeze({ tries: REATTACH_MAX_TRIES, drainingStreak: 0, drainingTotal: DRAINING_TOTAL_MAX });
6
8
  const isCount = (v) => typeof v === 'number' && Number.isSafeInteger(v) && v >= 0;
7
- const giveUp = (reason, budget) => Object.freeze({ action: 'give_up', reason, budget });
9
+ const giveUp = (notFollowed, budget) => Object.freeze({ action: 'give_up', budget, notFollowed });
8
10
  const reattach = (afterMs, tries, drainingStreak, drainingTotal) => Object.freeze({ action: 'reattach', afterMs, budget: Object.freeze({ tries, drainingStreak, drainingTotal }) });
9
11
  export function runReattachPolicy(budget, event) {
12
+ let handle;
10
13
  try {
11
- if (typeof budget !== 'object' || budget === null || !isCount(budget.tries) || !isCount(budget.drainingStreak) || !isCount(budget.drainingTotal))
12
- return giveUp('bad_input', REATTACH_BUDGET_FRESH);
13
- if (typeof event !== 'object' || event === null || typeof event.hasRunId !== 'boolean')
14
- return giveUp('bad_input', budget);
15
- if (!event.hasRunId)
16
- return giveUp('no_run_id', budget);
14
+ const eventOk = typeof event === 'object' && event !== null;
15
+ const runId = eventOk ? event.runId : undefined;
16
+ handle = typeof runId === 'string' && runId !== '' ? runId : undefined;
17
+ const budgetOk = typeof budget === 'object' && budget !== null && isCount(budget.tries) && isCount(budget.drainingStreak) && isCount(budget.drainingTotal);
18
+ const keep = budgetOk ? budget : REATTACH_BUDGET_FRESH;
19
+ if (!eventOk)
20
+ return giveUp(mintRunNotFollowed('bad_input'), keep);
21
+ const bad = () => giveUp(mintRunNotFollowed('bad_input', handle), keep);
22
+ if (!budgetOk)
23
+ return bad();
24
+ const cause = event.cause;
25
+ if (cause !== 'connection_lost' && cause !== 'stream_cap' && cause !== 'stream_draining')
26
+ return bad();
27
+ let draining;
28
+ if (cause === 'stream_draining') {
29
+ const ladder = event.ladder;
30
+ const frame = event.frame;
31
+ if ((ladder === undefined) === (frame === undefined))
32
+ return bad();
33
+ if (ladder !== undefined) {
34
+ if (ladder !== 'exhausted')
35
+ return bad();
36
+ draining = { ladder: true };
37
+ }
38
+ else {
39
+ const madeProgress = event.madeProgress;
40
+ const streak = madeProgress === true ? 0 : budget.drainingStreak;
41
+ const st = drainingLegStep(streak, frame);
42
+ if ('giveUp' in st.decision && st.decision.giveUp === 'not_draining_end')
43
+ return bad();
44
+ draining = { step: st };
45
+ }
46
+ }
47
+ if (runId === undefined)
48
+ return giveUp(mintRunNotFollowed('no_run_id'), keep);
49
+ if (handle === undefined)
50
+ return bad();
51
+ const exhausted = (brk) => giveUp(mintRunNotFollowed('reattach_exhausted', handle, brk), budget);
17
52
  const { tries, drainingStreak, drainingTotal } = budget;
18
- if (event.cause === 'connection_lost') {
53
+ if (cause === 'connection_lost') {
19
54
  if (tries >= REATTACH_MAX_TRIES)
20
- return giveUp('budget_exhausted', budget);
55
+ return exhausted(cause);
56
+ const error = event.error;
21
57
  const next = tries + 1;
22
- const afterMs = isPreStreamDrainingReject(event.error) ? drainingRetryDelayMs(event.error, next - 1) : 1000 * next;
58
+ const afterMs = isPreStreamDrainingReject(error) ? drainingRetryDelayMs(error, next - 1) : 1000 * next;
23
59
  return reattach(afterMs, next, 0, drainingTotal);
24
60
  }
25
- if (event.cause === 'stream_cap') {
61
+ if (cause === 'stream_cap') {
26
62
  if (tries >= REATTACH_MAX_TRIES)
27
- return giveUp('budget_exhausted', budget);
63
+ return exhausted(cause);
28
64
  return reattach(0, tries + 1, 0, drainingTotal);
29
65
  }
30
- if (event.cause === 'stream_draining') {
31
- const streak = event.madeProgress === true ? 0 : drainingStreak;
32
- const { decision, streak: nextStreak } = drainingLegStep(streak, event.frame);
33
- if ('reconnectAfterMs' in decision && drainingTotal < DRAINING_TOTAL_MAX)
34
- return reattach(decision.reconnectAfterMs, tries, nextStreak, drainingTotal + 1);
35
- if ('giveUp' in decision && decision.giveUp === 'not_draining_end')
36
- return giveUp('bad_input', budget);
66
+ if (draining === undefined || 'ladder' in draining) {
37
67
  if (tries >= REATTACH_MAX_TRIES)
38
- return giveUp('budget_exhausted', budget);
39
- return reattach(0, tries + 1, nextStreak, drainingTotal);
68
+ return exhausted(cause);
69
+ return reattach(0, tries + 1, drainingStreak, drainingTotal);
40
70
  }
41
- return giveUp('bad_input', budget);
71
+ const { decision, streak: nextStreak } = draining.step;
72
+ if ('reconnectAfterMs' in decision && drainingTotal < DRAINING_TOTAL_MAX)
73
+ return reattach(decision.reconnectAfterMs, tries, nextStreak, drainingTotal + 1);
74
+ if (tries >= REATTACH_MAX_TRIES)
75
+ return exhausted(cause);
76
+ return reattach(0, tries + 1, nextStreak, drainingTotal);
42
77
  }
43
78
  catch {
44
- return giveUp('bad_input', REATTACH_BUDGET_FRESH);
79
+ return giveUp(mintRunNotFollowed('bad_input', handle), REATTACH_BUDGET_FRESH);
45
80
  }
46
81
  }
package/dist/seam.d.ts CHANGED
@@ -5,6 +5,7 @@ import type { WiringManifestAutoMode, WiringManifestModelGate, WiringManifestMcp
5
5
  import type { ToolRosterView, ToolRosterDeltaView, WiringManifestHandsView } from './toolRoster.js';
6
6
  import type { WiringManifestFleetView } from './adapter/downstream/wiringManifestView.js';
7
7
  import type { SuspendedReopenReading } from './hitl/suspendedReopen.js';
8
+ import type { PlanReviewCardFacts } from './planReviewCardFacts.js';
8
9
  import type { ToolKillCause } from './toolResult.js';
9
10
  import type { HumanInputAttachmentView } from './ownKey.js';
10
11
  import type { TaskNotificationFields } from './notifications.js';
@@ -117,6 +118,7 @@ export type ChromeEvent = {
117
118
  summary: string;
118
119
  message: string;
119
120
  notification: Readonly<TaskNotificationFields>;
121
+ diagnostics?: string;
120
122
  } | {
121
123
  kind: 'task_ledger_sync';
122
124
  laneProof: LaneProof;
@@ -159,6 +161,7 @@ export type ChromeEvent = {
159
161
  kind: 'plan_review_park';
160
162
  laneProof: LaneProof;
161
163
  result: unknown;
164
+ planReview?: PlanReviewCardFacts;
162
165
  } | ({
163
166
  kind: 'run_cost_reconciled';
164
167
  laneProof: LaneProof;
package/dist/seam.js CHANGED
@@ -32,7 +32,7 @@ const CHROME_ARM_TABLE = {
32
32
  '🔴 `usageMissing` 在场时同行 usage 是**下界**,渲「≥」不渲已知账;' +
33
33
  '🔴 终帧的 `_sema_nested_usage_by_task` 是同一份账的**收口快照**,两者**不相加**(本臂是增量、那是合计)',
34
34
  },
35
- plan_review_park: { required: true, duty: '弹 plan 审批卡(fail-soft,不挡后续终态帧)' },
35
+ plan_review_park: { required: true, duty: '弹 plan 审批卡(fail-soft,不挡后续终态帧);卡事实 `planReview` 在场时按它出选项与原因句(只有 shown 给批准档)' },
36
36
  run_cost_reconciled: {
37
37
  required: false,
38
38
  duty: '可选:终局成本对账(own / nested / compaction 三段 + reconciled = own + nested)。' +
@@ -1254,6 +1254,7 @@ const SUBAGENT_CONTENT_SHAPE = shapeRule("subagent content", {
1254
1254
  args: optF({ label: "passthrough", test: () => true }),
1255
1255
  output: optF(R_STR),
1256
1256
  isError: optF(R_BOOL),
1257
+ notExecuted: optF(R_TRUE),
1257
1258
  });
1258
1259
  export function readSeatSubagentContent(event) {
1259
1260
  try {
@@ -33,7 +33,7 @@ export type SessionMemoryEraseAnswer = {
33
33
  detail: string;
34
34
  };
35
35
  export declare function eraseSessionMemory(call: SessionMemoryEraseCall, wire: SessionMemoryEraseWire, opts?: SessionMemoryEraseOptions): Promise<SessionMemoryEraseAnswer>;
36
- export type SessionMemoryEraseRefusalReason = 'bad_input' | 'no_connection' | 'cancelled_before_send' | 'credentials_required' | 'not_permitted' | 'memory_engine_absent' | 'session_not_found' | 'route_absent' | 'request_rejected' | 'evidence_unavailable' | 'request_id_reused' | 'store_unreadable';
36
+ export type SessionMemoryEraseRefusalReason = 'bad_input' | 'no_connection' | 'cancelled_before_send' | 'credentials_required' | 'not_permitted' | 'memory_engine_absent' | 'session_not_found' | 'route_absent' | 'request_rejected' | 'evidence_unavailable' | 'request_id_reused' | 'store_unreadable' | 'personal_store_unjudged';
37
37
  export type SessionMemoryEraseUnconfirmedWhy = 'no_answer' | 'receipt_unreadable' | 'receipt_mismatch' | 'store_link_outside' | 'store_home_not_directory' | 'control_plane_corrupt' | 'engine_fault' | 'unclassified' | 'no_code' | 'unreadable';
38
38
  export type SessionMemoryEraseRetry = 'not_needed' | 'same_request_id' | 'new_request_id' | 'needs_operator' | 'needs_consent' | 'human_decision' | 'pointless';
39
39
  type SessionMemoryEraseReceipt = Extract<MemoryEraseReceiptReading, {
@@ -3,6 +3,7 @@ import { INJECTED_WIRE_SETTLE_MS, connectionLostDetail, noAnswerDetailFrom, noAn
3
3
  import { memoryEraseReplayPosture, readMemoryEraseReceipt } from './memoryEntriesWire.js';
4
4
  import { normalizeWirePrincipal } from './principalWire.js';
5
5
  import { isSessionIdPathSegment } from './sessionIdSegment.js';
6
+ import { msg, renderMessage } from './i18n/translate.js';
6
7
  const PRINCIPAL_HEADER = 'x-agent-principal';
7
8
  const MAX_TIMER_MS = 2_147_483_647;
8
9
  const NOT_SENT = ' — nothing was sent';
@@ -231,6 +232,7 @@ const REFUSAL_TEXT = {
231
232
  evidence_unavailable: "Nothing was erased: this deployment's memory store cannot keep proof of an erase, so the erase was declined.",
232
233
  request_id_reused: 'Nothing was erased: this request id was already used for a different erase.',
233
234
  store_unreadable: 'Nothing was erased: part of the memory store could not be read, so the erase was declined before anything changed; the deployment operator needs to repair the store.',
235
+ personal_store_unjudged: renderMessage(msg('session.memoryErase.personalStoreUnjudged')),
234
236
  };
235
237
  const UNCONFIRMED_TEXT = {
236
238
  no_answer: 'Not confirmed: no answer came back from the engine, so the erase may or may not have happened.',
@@ -290,6 +292,7 @@ const REFUSAL_RETRY = {
290
292
  evidence_unavailable: 'needs_consent',
291
293
  request_id_reused: 'new_request_id',
292
294
  store_unreadable: 'needs_operator',
295
+ personal_store_unjudged: 'pointless',
293
296
  };
294
297
  export const SESSION_MEMORY_ERASE_REFUSAL_REASONS = Object.freeze(Object.keys(REFUSAL_TEXT));
295
298
  export const SESSION_MEMORY_ERASE_UNCONFIRMED_WHYS = Object.freeze(Object.keys(UNCONFIRMED_TEXT));
@@ -350,6 +353,8 @@ function bandOfCode(code) {
350
353
  return { refused: 'request_id_reused' };
351
354
  case 'memory.erasure_census_incomplete':
352
355
  return { refused: 'store_unreadable' };
356
+ case 'memory.erasure_store_unjudged':
357
+ return { refused: 'personal_store_unjudged' };
353
358
  case 'memory.target_outside_store':
354
359
  return { unconfirmed: 'store_link_outside' };
355
360
  case 'memory.scope_home_not_directory':
@@ -1,4 +1,5 @@
1
1
  import type { SessionMemoryStatus, SessionMemoryStatusResponse } from '@sema-agent/sdk';
2
+ import { type UiLanguage } from './env/uiLanguage.js';
2
3
  export interface MemoryStatusClientLike {
3
4
  sessions: {
4
5
  memoryStatus(sessionId: string, opts?: {
@@ -8,7 +9,17 @@ export interface MemoryStatusClientLike {
8
9
  } & SessionMemoryStatus>;
9
10
  };
10
11
  }
11
- export type MemoryStatusFacts = Omit<SessionMemoryStatusResponse, 'sessionId'>;
12
+ export type MemoryStatusFacts = Omit<SessionMemoryStatusResponse, 'sessionId' | 'memorySync'> & {
13
+ readonly memorySync?: MemorySyncFacts;
14
+ };
15
+ export interface MemorySyncFacts {
16
+ readonly parked: readonly MemorySyncParkedFacts[];
17
+ }
18
+ export interface MemorySyncParkedFacts {
19
+ readonly scope: string;
20
+ readonly wayOut: string;
21
+ }
22
+ export declare const MEMORY_SYNC_WAY_OUT_DELETE_CURSOR: "delete_cursor";
12
23
  export type MemoryStatusVerdict = {
13
24
  readonly kind: 'ok';
14
25
  readonly sessionId: string;
@@ -44,4 +55,19 @@ export declare function readLastCapture(s: SessionMemoryStatus): LastCaptureRead
44
55
  export declare function readSessionMemoryStatus(client: MemoryStatusClientLike, sessionId: string, opts?: {
45
56
  signal?: AbortSignal;
46
57
  }): Promise<MemoryStatusVerdict>;
58
+ export type MemorySyncParkingReading = {
59
+ readonly state: 'syncing';
60
+ } | {
61
+ readonly state: 'parked';
62
+ readonly layers: readonly MemorySyncParkedLayerReading[];
63
+ } | {
64
+ readonly state: 'not_reported';
65
+ };
66
+ export interface MemorySyncParkedLayerReading {
67
+ readonly scope: string;
68
+ readonly wayOut: string;
69
+ readonly deleteCursor: boolean;
70
+ }
71
+ export declare function readMemorySyncParking(facts: MemoryStatusFacts | null | undefined): MemorySyncParkingReading;
72
+ export declare function memorySyncParkingNotice(reading: MemorySyncParkingReading, lang?: UiLanguage | (string & {})): string | undefined;
47
73
  export declare function readAutoConsolidationArmed(facts: MemoryStatusFacts | null | undefined): 'armed' | 'unarmed' | 'indeterminate';
@@ -1,5 +1,9 @@
1
1
  import { isSessionIdPathSegment } from './sessionIdSegment.js';
2
2
  import { readThrownSlot, THROWN_SLOT_UNREADABLE } from './wireFailureShape.js';
3
+ import { isUiLanguage } from './env/uiLanguage.js';
4
+ import { msg, renderMessage } from './i18n/translate.js';
5
+ export const MEMORY_SYNC_WAY_OUT_DELETE_CURSOR = 'delete_cursor';
6
+ const MEMORY_SYNC_PARKED_READ_MAX = 256;
3
7
  export function classifyMemoryStatusFailure(e) {
4
8
  const rawCode = readThrownSlot(e, 'errorCode', 'data');
5
9
  const rawStatus = readThrownSlot(e, 'status', 'data');
@@ -64,6 +68,7 @@ export async function readSessionMemoryStatus(client, sessionId, opts) {
64
68
  : {}),
65
69
  ...(typeof o.lastCaptureAt === 'number' ? { lastCaptureAt: o.lastCaptureAt } : {}),
66
70
  ...(typeof o.autoConsolidationArmed === 'boolean' ? { autoConsolidationArmed: o.autoConsolidationArmed } : {}),
71
+ ...memorySyncSpread(o),
67
72
  },
68
73
  };
69
74
  }
@@ -71,6 +76,118 @@ export async function readSessionMemoryStatus(client, sessionId, opts) {
71
76
  return classifyMemoryStatusFailure(e);
72
77
  }
73
78
  }
79
+ function ownDataSlot(o, key) {
80
+ try {
81
+ const d = Object.getOwnPropertyDescriptor(o, key);
82
+ if (d === undefined)
83
+ return { ok: true, value: undefined };
84
+ return Object.hasOwn(d, 'value') ? { ok: true, value: d.value } : { ok: false };
85
+ }
86
+ catch {
87
+ return { ok: false };
88
+ }
89
+ }
90
+ const isPlainObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
91
+ function memorySyncFactsOf(raw) {
92
+ try {
93
+ if (!isPlainObject(raw))
94
+ return undefined;
95
+ const parkedSlot = ownDataSlot(raw, 'parked');
96
+ if (!parkedSlot.ok || !Array.isArray(parkedSlot.value))
97
+ return undefined;
98
+ const arr = parkedSlot.value;
99
+ const len = ownDataSlot(arr, 'length');
100
+ if (!len.ok || typeof len.value !== 'number' || !Number.isSafeInteger(len.value) || len.value < 0 || len.value > MEMORY_SYNC_PARKED_READ_MAX)
101
+ return undefined;
102
+ const parked = [];
103
+ for (let i = 0; i < len.value; i++) {
104
+ const el = ownDataSlot(arr, String(i));
105
+ if (!el.ok || !isPlainObject(el.value))
106
+ return undefined;
107
+ const scope = ownDataSlot(el.value, 'scope');
108
+ const wayOut = ownDataSlot(el.value, 'wayOut');
109
+ if (!scope.ok || !wayOut.ok || typeof scope.value !== 'string' || scope.value.length === 0 || typeof wayOut.value !== 'string' || wayOut.value.length === 0)
110
+ return undefined;
111
+ parked.push(Object.freeze({ scope: scope.value, wayOut: wayOut.value }));
112
+ }
113
+ return Object.freeze({ parked: Object.freeze(parked) });
114
+ }
115
+ catch {
116
+ return undefined;
117
+ }
118
+ }
119
+ function memorySyncSpread(o) {
120
+ const slot = ownDataSlot(o, 'memorySync');
121
+ if (!slot.ok || slot.value === undefined)
122
+ return {};
123
+ const facts = memorySyncFactsOf(slot.value);
124
+ return facts === undefined ? {} : { memorySync: facts };
125
+ }
126
+ export function readMemorySyncParking(facts) {
127
+ let sync;
128
+ try {
129
+ if (!isPlainObject(facts))
130
+ return Object.freeze({ state: 'not_reported' });
131
+ const slot = ownDataSlot(facts, 'memorySync');
132
+ sync = slot.ok ? memorySyncFactsOf(slot.value) : undefined;
133
+ }
134
+ catch {
135
+ sync = undefined;
136
+ }
137
+ if (sync === undefined)
138
+ return Object.freeze({ state: 'not_reported' });
139
+ if (sync.parked.length === 0)
140
+ return Object.freeze({ state: 'syncing' });
141
+ return Object.freeze({
142
+ state: 'parked',
143
+ layers: Object.freeze(sync.parked.map((p) => Object.freeze({ scope: p.scope, wayOut: p.wayOut, deleteCursor: p.wayOut === MEMORY_SYNC_WAY_OUT_DELETE_CURSOR }))),
144
+ });
145
+ }
146
+ const MEMORY_SYNC_SCOPE_NAME_MAX = 120;
147
+ export function memorySyncParkingNotice(reading, lang) {
148
+ try {
149
+ if (typeof reading !== 'object' || reading === null)
150
+ return undefined;
151
+ const state = Object.getOwnPropertyDescriptor(reading, 'state');
152
+ if (state === undefined || !Object.hasOwn(state, 'value') || state.value !== 'parked')
153
+ return undefined;
154
+ const layersSlot = Object.getOwnPropertyDescriptor(reading, 'layers');
155
+ if (layersSlot === undefined || !Object.hasOwn(layersSlot, 'value') || !Array.isArray(layersSlot.value))
156
+ return undefined;
157
+ const raw = layersSlot.value;
158
+ const lenSlot = Object.getOwnPropertyDescriptor(raw, 'length');
159
+ const count = lenSlot?.value;
160
+ if (typeof count !== 'number' || !Number.isSafeInteger(count) || count < 1 || count > MEMORY_SYNC_PARKED_READ_MAX)
161
+ return undefined;
162
+ const scopes = [];
163
+ let named = true;
164
+ let allDeleteCursor = true;
165
+ for (let i = 0; i < count; i++) {
166
+ const el = Object.getOwnPropertyDescriptor(raw, String(i));
167
+ const layer = el !== undefined && Object.hasOwn(el, 'value') ? el.value : undefined;
168
+ if (typeof layer !== 'object' || layer === null)
169
+ return undefined;
170
+ const scope = Object.getOwnPropertyDescriptor(layer, 'scope');
171
+ const del = Object.getOwnPropertyDescriptor(layer, 'deleteCursor');
172
+ const s = scope !== undefined && Object.hasOwn(scope, 'value') ? scope.value : undefined;
173
+ if (typeof s !== 'string' || s.length === 0 || s.length > MEMORY_SYNC_SCOPE_NAME_MAX || /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u.test(s))
174
+ named = false;
175
+ else
176
+ scopes.push(s);
177
+ if (del === undefined || !Object.hasOwn(del, 'value') || del.value !== true)
178
+ allDeleteCursor = false;
179
+ }
180
+ const wayOut = msg(allDeleteCursor ? 'session.memorySync.wayOut.deleteCursor' : 'session.memorySync.wayOut.unrecognized');
181
+ if (!named)
182
+ return renderMessage(msg(count === 1 ? 'session.memorySync.parkedUnnamed.one' : 'session.memorySync.parkedUnnamed.other', { count, wayOut }), isUiLanguage(lang) ? lang : undefined);
183
+ if (count === 1)
184
+ return renderMessage(msg('session.memorySync.parked.one', { scope: scopes[0] ?? '', wayOut }), isUiLanguage(lang) ? lang : undefined);
185
+ return renderMessage(msg('session.memorySync.parked.other', { count, scopes: scopes.join(lang === 'zh' ? '、' : ', '), wayOut }), isUiLanguage(lang) ? lang : undefined);
186
+ }
187
+ catch {
188
+ return undefined;
189
+ }
190
+ }
74
191
  export function readAutoConsolidationArmed(facts) {
75
192
  if (typeof facts !== 'object' || facts === null)
76
193
  return 'indeterminate';
@@ -1,4 +1,5 @@
1
1
  import { type EnvLike } from './hostEnv.js';
2
+ import { type RunNotFollowed } from './runFollow.js';
2
3
  export declare const SSE_HEARTBEAT_GRACE_ENV = "SEMA_SSE_HEARTBEAT_GRACE_MS";
3
4
  export declare const SSE_GRACE_MIN_MS = 5000;
4
5
  export declare const SSE_GRACE_MAX_MS = 600000;
@@ -7,9 +8,10 @@ export declare function resolveSseHeartbeatGraceMs(env?: EnvLike): number | unde
7
8
  export declare function markEngineAliveIdle<E extends Error>(err: E): E;
8
9
  export declare function engineAliveIdleElapsedMs(err: unknown): number | undefined;
9
10
  export declare function isSseIdleError(err: unknown): boolean;
11
+ export declare function isStreamEndedWithoutTerminalError(err: {} | null | undefined): boolean;
10
12
  export declare function probeEngineAlive(baseUrl: string, timeoutMs?: number): Promise<boolean>;
11
13
  export declare function diagnoseSseIdleTear<T>(inner: AsyncGenerator<T>, baseUrl: string, signal?: AbortSignal): AsyncGenerator<T>;
12
14
  export declare function engineAliveIdleContent(elapsedMs: number, detach?: {
13
15
  detached: true;
14
16
  taskId?: string;
15
- }): string;
17
+ }, notFollowed?: RunNotFollowed): string;
@@ -1,6 +1,8 @@
1
1
  import { hostEnv } from './hostEnv.js';
2
- import { SseIdleError, probeHealth as sdkProbeHealth } from '@sema-agent/sdk';
2
+ import { SseIdleError, StreamEndedWithoutTerminalError, probeHealth as sdkProbeHealth } from '@sema-agent/sdk';
3
3
  import { readThrownSlot, thrownIsInstanceOf } from './wireFailureShape.js';
4
+ import { msg, renderMessage } from './i18n/translate.js';
5
+ import { runNotFollowedNoticeRef } from './runFollow.js';
4
6
  export const SSE_HEARTBEAT_GRACE_ENV = 'SEMA_SSE_HEARTBEAT_GRACE_MS';
5
7
  export const SSE_GRACE_MIN_MS = 5_000;
6
8
  export const SSE_GRACE_MAX_MS = 600_000;
@@ -17,8 +19,12 @@ export function resolveSseHeartbeatGraceMs(env = hostEnv()) {
17
19
  return n;
18
20
  }
19
21
  export function markEngineAliveIdle(err) {
20
- ;
21
- err.semaTearVerdict = 'engine-alive-idle';
22
+ try {
23
+ ;
24
+ err.semaTearVerdict = 'engine-alive-idle';
25
+ }
26
+ catch {
27
+ }
22
28
  return err;
23
29
  }
24
30
  export function engineAliveIdleElapsedMs(err) {
@@ -32,6 +38,11 @@ export function isSseIdleError(err) {
32
38
  return true;
33
39
  return readThrownSlot(err, 'name', 'data') === 'SseIdleError';
34
40
  }
41
+ export function isStreamEndedWithoutTerminalError(err) {
42
+ if (thrownIsInstanceOf(err, StreamEndedWithoutTerminalError))
43
+ return true;
44
+ return readThrownSlot(err, 'name', 'data') === 'StreamEndedWithoutTerminalError';
45
+ }
35
46
  export async function probeEngineAlive(baseUrl, timeoutMs = 2_000) {
36
47
  return (await sdkProbeHealth(baseUrl, timeoutMs)).alive;
37
48
  }
@@ -46,8 +57,12 @@ export async function* diagnoseSseIdleTear(inner, baseUrl, signal) {
46
57
  throw err;
47
58
  }
48
59
  }
49
- export function engineAliveIdleContent(elapsedMs, detach) {
60
+ export function engineAliveIdleContent(elapsedMs, detach, notFollowed) {
50
61
  const secs = Math.max(1, Math.round((elapsedMs > 0 ? elapsedMs : SDK_DEFAULT_GRACE_MS) / 1000));
62
+ if (notFollowed !== undefined && notFollowed !== null) {
63
+ const grace = msg('follow.notFollowed.idleGrace', { env: SSE_HEARTBEAT_GRACE_ENV, min: SSE_GRACE_MIN_MS, max: SSE_GRACE_MAX_MS, default: SDK_DEFAULT_GRACE_MS });
64
+ return renderMessage(msg('follow.notFollowed.idleTear', { secs, notice: runNotFollowedNoticeRef(notFollowed), grace }));
65
+ }
51
66
  const graceTail = `If your tasks legitimately go quiet this long (e.g. a long build), raise the idle grace: set ${SSE_HEARTBEAT_GRACE_ENV} ` +
52
67
  `(settings env, integer ms, ${SSE_GRACE_MIN_MS}-${SSE_GRACE_MAX_MS}; default ${SDK_DEFAULT_GRACE_MS}).`;
53
68
  if (detach?.detached) {
@@ -11,6 +11,7 @@ export interface EngineTaskStatusAttachment {
11
11
  status: 'pending' | 'running' | 'completed' | 'failed' | 'killed' | 'parked';
12
12
  description: string;
13
13
  deltaSummary: null;
14
+ read?: string;
14
15
  }
15
16
  export type SteeringRenderable = SteeringInjectedAttachment | EngineTaskStatusAttachment;
16
17
  export declare function steeringLabel(source: string): string;
package/dist/steering.js CHANGED
@@ -25,37 +25,45 @@ const PHRASE_TO_STATUS = [
25
25
  ['failed', 'failed'],
26
26
  ['killed', 'killed'],
27
27
  ];
28
+ const BACKGROUND_TASK_LINE = new RegExp('^- \\[([^\\]]+)\\] Task "([\\s\\S]*)" (' +
29
+ PHRASE_TO_STATUS.map(([phrase]) => phrase.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|') +
30
+ ')(?: — ((?:(?!" )[\\s\\S])*))?$');
28
31
  function parseBackgroundTaskLine(line) {
29
- const trimmed = line.trim();
30
- for (const [phrase, status] of PHRASE_TO_STATUS) {
31
- const suffix = `" ${phrase}`;
32
- if (!trimmed.endsWith(suffix))
33
- continue;
34
- const head = trimmed.slice(0, trimmed.length - suffix.length);
35
- const m = /^- \[([^\]]+)\] Task "([\s\S]*)$/.exec(head);
36
- if (!m)
37
- return null;
38
- const taskId = m[1];
39
- const description = m[2];
40
- if (taskId === undefined || description === undefined)
41
- return null;
42
- return {
43
- type: 'task_status',
44
- taskId,
45
- taskType: 'local_bash',
46
- status,
47
- description,
48
- deltaSummary: null,
49
- };
32
+ const m = BACKGROUND_TASK_LINE.exec(line.trim());
33
+ if (!m)
34
+ return null;
35
+ const taskId = m[1];
36
+ const description = m[2];
37
+ const status = PHRASE_TO_STATUS.find(([phrase]) => phrase === m[3])?.[1];
38
+ if (taskId === undefined || description === undefined || status === undefined)
39
+ return null;
40
+ const read = m[4];
41
+ return {
42
+ type: 'task_status',
43
+ taskId,
44
+ taskType: 'local_bash',
45
+ status,
46
+ description,
47
+ deltaSummary: null,
48
+ ...(read !== undefined ? { read } : {}),
49
+ };
50
+ }
51
+ function rowBoundariesTrusted(lines) {
52
+ let last = lines.length - 1;
53
+ while (last >= 0 && (lines[last] ?? '').trim() === '')
54
+ last--;
55
+ for (let i = 0; i < last; i++) {
56
+ if (/^- \[[^\]]+\] Task "[^"]*$/.test((lines[i] ?? '').trim()))
57
+ return false;
50
58
  }
51
- return null;
59
+ return true;
52
60
  }
53
61
  export function steeringInjectedToAttachments(source, preview) {
54
62
  if (source === 'background_tasks') {
55
- const tasks = preview
56
- .split('\n')
57
- .map(parseBackgroundTaskLine)
58
- .filter((t) => t !== null);
63
+ const lines = preview.split('\n');
64
+ const tasks = rowBoundariesTrusted(lines)
65
+ ? lines.map(parseBackgroundTaskLine).filter((t) => t !== null)
66
+ : [];
59
67
  if (tasks.length > 0)
60
68
  return tasks;
61
69
  }
@@ -6,6 +6,7 @@ import { engineSessionParamFor } from '../engineSessionParam.js';
6
6
  import { resolveOwnerContext } from './engineSubagentResume.js';
7
7
  import { coerceOutput, parentToolCallIdOf, publishSubagentContentEvent, settleSubagentContent, } from '../subagentContentStore.js';
8
8
  import { noteBgOwnerAbsence } from './subagentOwnerAbsence.js';
9
+ import { readFrameTrueMark } from '../ownKey.js';
9
10
  function usableTailClient(client) {
10
11
  return clientHasVerbs(client, [['capabilities'], ['runs', 'subagentStream']]);
11
12
  }
@@ -140,6 +141,7 @@ export function tailEngineSubagent(taskId, opts) {
140
141
  ...(typeof d.toolName === 'string' ? { toolName: d.toolName } : {}),
141
142
  ...(out !== undefined ? { output: out } : {}),
142
143
  ...(d.isError === true ? { isError: true } : {}),
144
+ ...(readFrameTrueMark(d, 'notExecuted') ? { notExecuted: true } : {}),
143
145
  });
144
146
  }
145
147
  else if (t === 'task_settled') {
@@ -11,6 +11,7 @@ export type SubagentContentItem = {
11
11
  input: unknown;
12
12
  output?: string;
13
13
  isError?: boolean;
14
+ notExecuted?: true;
14
15
  } | {
15
16
  kind: 'echo';
16
17
  text: string;
@@ -31,6 +32,7 @@ export interface SubagentContentEvent {
31
32
  args?: unknown;
32
33
  output?: string | undefined;
33
34
  isError?: boolean | undefined;
35
+ notExecuted?: true | undefined;
34
36
  }
35
37
  export declare function coerceOutput(v: unknown): string | undefined;
36
38
  export declare function subagentContentEventOf(ev: {
@@ -82,6 +84,7 @@ export type SubagentViewSlot = {
82
84
  input: unknown;
83
85
  output?: string;
84
86
  isError?: boolean;
87
+ notExecuted?: true;
85
88
  };
86
89
  export declare function planSubagentViewSlots(taskId: string): SubagentViewSlot[];
87
90
  export declare function parentToolCallIdOf(taskId: string): string | undefined;
@@ -1,4 +1,5 @@
1
1
  import { DEFAULT_SESSION_KEY } from './sessionSlot.js';
2
+ import { readFrameTrueMark } from './ownKey.js';
2
3
  import { toolEndOutputText } from './toolResultContent.js';
3
4
  export function coerceOutput(v) {
4
5
  return toolEndOutputText(v);
@@ -23,6 +24,7 @@ export function subagentContentEventOf(ev) {
23
24
  args: sub.args,
24
25
  output: coerceOutput(sub.output),
25
26
  isError: sub.isError,
27
+ notExecuted: readFrameTrueMark(sub, 'notExecuted') ? true : undefined,
26
28
  };
27
29
  }
28
30
  const MAX_ITEMS_PER_TASK = 200;
@@ -567,12 +569,17 @@ export function publishSubagentContentEvent(ev) {
567
569
  const before = itemBytesOf(it);
568
570
  const outBefore = it.output;
569
571
  const errBefore = it.isError;
572
+ const notExecBefore = it.notExecuted;
570
573
  it.output = ev.output ?? '';
571
574
  it.isError = ev.isError === true;
575
+ if (ev.notExecuted === true)
576
+ it.notExecuted = true;
577
+ else
578
+ delete it.notExecuted;
572
579
  const delta = itemBytesOf(it) - before;
573
580
  s.itemBytes[at] = (s.itemBytes[at] ?? 0) + delta;
574
581
  addBytes(s, delta);
575
- stampPhase(s, it.id, 'end', ev.eventId, it.output !== outBefore || it.isError !== errBefore);
582
+ stampPhase(s, it.id, 'end', ev.eventId, it.output !== outBefore || it.isError !== errBefore || it.notExecuted !== notExecBefore);
576
583
  break;
577
584
  }
578
585
  const synthId = id ?? `${ev.taskId}-tool-${s.items.length}`;
@@ -583,6 +590,7 @@ export function publishSubagentContentEvent(ev) {
583
590
  input: ev.args ?? {},
584
591
  output: ev.output ?? '',
585
592
  isError: ev.isError === true,
593
+ ...(ev.notExecuted === true ? { notExecuted: true } : {}),
586
594
  });
587
595
  recordCut(s);
588
596
  stampPhase(s, synthId, 'end', ev.eventId, true);
@@ -626,6 +634,7 @@ export function planSubagentViewSlots(taskId) {
626
634
  input: it.input,
627
635
  ...(it.output !== undefined ? { output: it.output } : {}),
628
636
  ...(it.isError === true ? { isError: true } : {}),
637
+ ...(it.notExecuted === true ? { notExecuted: true } : {}),
629
638
  });
630
639
  }
631
640
  else if (it.kind === 'text') {
@@ -4,10 +4,13 @@ import { type LiveModelCatalog } from './liveModelCatalog.js';
4
4
  import type { TaskRequestLike } from './request/taskRequest.js';
5
5
  export type TurnFailureCategory = 'auth' | 'billing' | 'rate_limited' | 'invalid_request' | 'capability_unavailable' | 'timeout' | 'server_error' | 'connection_lost' | 'parked' | 'internal';
6
6
  export declare const TURN_FAILURE_CATEGORIES: readonly TurnFailureCategory[];
7
- export interface TurnFailureRetry {
7
+ export type TurnFailureRetry = {
8
8
  readonly offer: boolean;
9
9
  readonly basis: 'wire' | 'category';
10
- }
10
+ } | {
11
+ readonly offer: false;
12
+ readonly basis: 'not_followed';
13
+ };
11
14
  export interface TurnFailurePresentation {
12
15
  readonly category: TurnFailureCategory;
13
16
  readonly kind: TurnWireErrorVerdict['kind'];