@sema-agent/client-core 0.104.1 → 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 (86) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/README.md +11 -4
  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/planReviewCard.d.ts +21 -0
  33. package/dist/hitl/planReviewCard.js +229 -0
  34. package/dist/hitl/planReviewWire.d.ts +38 -13
  35. package/dist/hitl/planReviewWire.js +355 -133
  36. package/dist/hostSettingsSource.d.ts +1 -1
  37. package/dist/hostSettingsSource.js +8 -4
  38. package/dist/hostTurnSettings.d.ts +1 -1
  39. package/dist/hostTurnSettings.js +29 -10
  40. package/dist/i18n/messages.en.d.ts +50 -0
  41. package/dist/i18n/messages.en.js +50 -0
  42. package/dist/i18n/messages.zh.js +51 -0
  43. package/dist/index.d.ts +15 -3
  44. package/dist/index.js +9 -1
  45. package/dist/mcpTransportKindsCapability.d.ts +18 -0
  46. package/dist/mcpTransportKindsCapability.js +75 -0
  47. package/dist/mcpWireCaps.d.ts +4 -1
  48. package/dist/mcpWireCaps.js +24 -13
  49. package/dist/notifications.d.ts +9 -0
  50. package/dist/notifications.js +11 -2
  51. package/dist/ownKey.d.ts +4 -0
  52. package/dist/ownKey.js +8 -0
  53. package/dist/permissionModeWriteCapability.d.ts +13 -0
  54. package/dist/permissionModeWriteCapability.js +35 -0
  55. package/dist/permissionsAllowGrantCapability.d.ts +3 -0
  56. package/dist/permissionsAllowGrantCapability.js +26 -3
  57. package/dist/permissionsDeclaration.d.ts +23 -1
  58. package/dist/permissionsDeclaration.js +60 -30
  59. package/dist/planReviewCardFacts.d.ts +116 -0
  60. package/dist/planReviewCardFacts.js +469 -0
  61. package/dist/reattachWindow.d.ts +8 -1
  62. package/dist/reattachWindow.js +44 -12
  63. package/dist/runFollow.d.ts +30 -0
  64. package/dist/runFollow.js +117 -0
  65. package/dist/runLedgerSnapshot.d.ts +20 -0
  66. package/dist/runLedgerSnapshot.js +272 -0
  67. package/dist/runPermissionModeSwitch.d.ts +57 -0
  68. package/dist/runPermissionModeSwitch.js +135 -0
  69. package/dist/runReattachPolicy.d.ts +13 -5
  70. package/dist/runReattachPolicy.js +58 -23
  71. package/dist/seam.d.ts +3 -0
  72. package/dist/seam.js +1 -1
  73. package/dist/seatContract.js +1 -0
  74. package/dist/sessionMemoryErase.d.ts +1 -1
  75. package/dist/sessionMemoryErase.js +5 -0
  76. package/dist/sessionMemoryStatus.d.ts +27 -1
  77. package/dist/sessionMemoryStatus.js +117 -0
  78. package/dist/sseIdleTriage.d.ts +3 -1
  79. package/dist/sseIdleTriage.js +19 -4
  80. package/dist/subagent/engineSubagentTail.js +2 -0
  81. package/dist/subagentContentStore.d.ts +3 -0
  82. package/dist/subagentContentStore.js +10 -1
  83. package/dist/turnFailurePresentation.d.ts +5 -2
  84. package/dist/turnFailurePresentation.js +12 -4
  85. package/docs/INTEGRATION-CLIENTS.md +493 -10
  86. package/package.json +2 -2
@@ -0,0 +1,135 @@
1
+ import { atMostOnceVerdictOf } from './atMostOnce.js';
2
+ import { readPermissionModeReceiptSegment } from './adapter/downstream/wiringManifestView.js';
3
+ import { displayUntrusted } from './displayUntrusted.js';
4
+ import { SDK_PERMISSION_MODE_ADMISSION_FOLDS, SDK_PERMISSION_MODE_PERSIST_MISSES } from './generated/sdkFactTables.js';
5
+ import { msg, renderMessage } from './i18n/translate.js';
6
+ import { ownDataField } from './ownKey.js';
7
+ import { permissionModeWriteAvailable } from './permissionModeWriteCapability.js';
8
+ import { isDisplayableWireCode } from './wireFailureShape.js';
9
+ export const PERMISSION_MODE_SWITCH_RESULT_KEYS = Object.freeze(['taskId', 'sessionId', 'status', 'permissionMode', 'admission', 'persisted', 'persistMiss']);
10
+ const NOT_RUNNING_CODE = 'steering.not_running';
11
+ const PRE_SWITCH_CORRUPT_CODE = 'corrupt';
12
+ function guardedSlot(o, key) {
13
+ if (o === null || (typeof o !== 'object' && typeof o !== 'function'))
14
+ return undefined;
15
+ try {
16
+ return o[key];
17
+ }
18
+ catch {
19
+ return undefined;
20
+ }
21
+ }
22
+ const unavailable = () => Object.freeze({ kind: 'unavailable' });
23
+ const unreadableAnswer = () => Object.freeze({ kind: 'unconfirmed', why: 'unreadable-answer' });
24
+ function admissionOf(raw) {
25
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
26
+ return undefined;
27
+ const requested = ownDataField(raw, 'requested');
28
+ const admitted = ownDataField(raw, 'admitted');
29
+ const foldedBy = ownDataField(raw, 'foldedBy');
30
+ if (typeof requested !== 'string' || requested === '' || typeof admitted !== 'string' || admitted === '')
31
+ return undefined;
32
+ const fold = SDK_PERMISSION_MODE_ADMISSION_FOLDS.find((w) => w === foldedBy);
33
+ return fold === undefined ? undefined : Object.freeze({ requested, admitted, foldedBy: fold });
34
+ }
35
+ function switchOutcomeOf(taskId, sent, body) {
36
+ try {
37
+ if (body === null || typeof body !== 'object' || Array.isArray(body))
38
+ return unreadableAnswer();
39
+ const keys = PERMISSION_MODE_SWITCH_RESULT_KEYS;
40
+ for (const k of Reflect.ownKeys(body))
41
+ if (typeof k !== 'string' || !keys.includes(k))
42
+ return unreadableAnswer();
43
+ if (ownDataField(body, 'taskId') !== taskId)
44
+ return unreadableAnswer();
45
+ const sessionId = ownDataField(body, 'sessionId');
46
+ if (typeof sessionId !== 'string' || sessionId === '' || ownDataField(body, 'status') !== 'running')
47
+ return unreadableAnswer();
48
+ const permissionMode = readPermissionModeReceiptSegment(body, 'permissionMode');
49
+ if (permissionMode === undefined)
50
+ return unreadableAnswer();
51
+ const admissionRaw = ownDataField(body, 'admission');
52
+ const admission = admissionRaw === undefined ? undefined : admissionOf(admissionRaw);
53
+ if (admissionRaw !== undefined && admission === undefined)
54
+ return unreadableAnswer();
55
+ const asked = admission !== undefined ? admission.requested : permissionMode.requested;
56
+ if (asked !== undefined && asked !== sent)
57
+ return unreadableAnswer();
58
+ if (admission !== undefined && permissionMode.requested !== undefined && permissionMode.requested !== admission.admitted)
59
+ return unreadableAnswer();
60
+ const persisted = ownDataField(body, 'persisted');
61
+ const persistMiss = ownDataField(body, 'persistMiss');
62
+ const base = { kind: 'switched', taskId, sessionId, permissionMode, ...(admission !== undefined ? { admission } : {}) };
63
+ if (persisted === true && persistMiss === undefined)
64
+ return Object.freeze({ ...base, persisted: true });
65
+ const miss = persisted === false ? SDK_PERMISSION_MODE_PERSIST_MISSES.find((w) => w === persistMiss) : undefined;
66
+ return miss === undefined ? unreadableAnswer() : Object.freeze({ ...base, persisted: false, persistMiss: miss });
67
+ }
68
+ catch {
69
+ return unreadableAnswer();
70
+ }
71
+ }
72
+ function failureOutcomeOf(e) {
73
+ const v = atMostOnceVerdictOf(e);
74
+ if (v.status === 501)
75
+ return unavailable();
76
+ if (v.delivery === 'rejected' && v.status !== undefined) {
77
+ if (v.status === 404)
78
+ return Object.freeze({ kind: 'not-found' });
79
+ if (v.status === 409 && v.code === NOT_RUNNING_CODE)
80
+ return Object.freeze({ kind: 'not-running' });
81
+ return Object.freeze({ kind: 'refused', status: v.status, ...(v.code !== undefined ? { errorCode: v.code } : {}) });
82
+ }
83
+ if (v.status === 500 && v.code === PRE_SWITCH_CORRUPT_CODE)
84
+ return Object.freeze({ kind: 'refused', status: 500, errorCode: v.code });
85
+ return v.status !== undefined
86
+ ? Object.freeze({ kind: 'unconfirmed', why: 'server-error', status: v.status })
87
+ : Object.freeze({ kind: 'unconfirmed', why: 'transport' });
88
+ }
89
+ export async function setRunPermissionMode(runs, taskId, mode, opts = {}) {
90
+ if (!permissionModeWriteAvailable(guardedSlot(opts, 'permissionModeWrite')))
91
+ return unavailable();
92
+ const verb = guardedSlot(runs, 'setPermissionMode');
93
+ if (typeof verb !== 'function')
94
+ return unavailable();
95
+ const sessionId = guardedSlot(opts, 'sessionId');
96
+ const signal = guardedSlot(opts, 'signal');
97
+ const callOpts = {
98
+ ...(typeof sessionId === 'string' && sessionId.length > 0 ? { session: sessionId } : {}),
99
+ ...(signal !== undefined && signal !== null ? { signal: signal } : {}),
100
+ };
101
+ let body;
102
+ try {
103
+ body = await verb.call(runs, taskId, mode, callOpts);
104
+ }
105
+ catch (e) {
106
+ return failureOutcomeOf(e);
107
+ }
108
+ return switchOutcomeOf(taskId, mode, body);
109
+ }
110
+ export function runPermissionModeSwitchLine(outcome, lang) {
111
+ switch (outcome.kind) {
112
+ case 'switched': {
113
+ const mode = displayUntrusted(outcome.permissionMode.effective, { max: 64 });
114
+ const reason = outcome.admission !== undefined
115
+ ? msg(outcome.admission.foldedBy === 'entry_floor' ? 'session.permissionModeSwitch.reason.entryFloor' : 'session.permissionModeSwitch.reason.disableAutoMode')
116
+ : outcome.permissionMode.foldedBy !== undefined
117
+ ? msg('session.permissionModeSwitch.reason.engine')
118
+ : undefined;
119
+ const line = renderMessage(reason === undefined ? msg('session.permissionModeSwitch.applied', { mode }) : msg('session.permissionModeSwitch.folded', { mode, reason }), lang);
120
+ return outcome.persisted ? line : renderMessage(msg('session.permissionModeSwitch.notPersisted', { line }), lang);
121
+ }
122
+ case 'not-running':
123
+ return renderMessage(msg('session.permissionModeSwitch.notRunning'), lang);
124
+ case 'not-found':
125
+ return renderMessage(msg('session.permissionModeSwitch.notFound'), lang);
126
+ case 'refused':
127
+ return outcome.errorCode !== undefined && isDisplayableWireCode(outcome.errorCode)
128
+ ? renderMessage(msg('session.permissionModeSwitch.refusedWithCode', { status: String(outcome.status), code: outcome.errorCode }), lang)
129
+ : renderMessage(msg('session.permissionModeSwitch.refused', { status: String(outcome.status) }), lang);
130
+ case 'unconfirmed':
131
+ return renderMessage(msg('session.permissionModeSwitch.unconfirmed'), lang);
132
+ case 'unavailable':
133
+ return renderMessage(msg('session.permissionModeSwitch.unavailable'), lang);
134
+ }
135
+ }
@@ -1,3 +1,4 @@
1
+ import { type RunNotFollowed } from './runFollow.js';
1
2
  export declare const REATTACH_MAX_TRIES = 5;
2
3
  export interface ReattachBudget {
3
4
  readonly tries: number;
@@ -5,27 +6,34 @@ export interface ReattachBudget {
5
6
  readonly drainingTotal: number;
6
7
  }
7
8
  export declare const REATTACH_BUDGET_FRESH: ReattachBudget;
9
+ export declare const REATTACH_BUDGET_NONE: ReattachBudget;
8
10
  export type ReattachEvent = {
9
11
  readonly cause: 'connection_lost';
10
- readonly hasRunId: boolean;
12
+ readonly runId?: string;
11
13
  readonly error?: object;
12
14
  } | {
13
15
  readonly cause: 'stream_cap';
14
- readonly hasRunId: boolean;
16
+ readonly runId?: string;
15
17
  } | {
16
18
  readonly cause: 'stream_draining';
17
- readonly hasRunId: boolean;
19
+ readonly runId?: string;
18
20
  readonly frame: object;
19
21
  readonly madeProgress?: boolean;
22
+ } | {
23
+ readonly cause: 'stream_draining';
24
+ readonly runId?: string;
25
+ readonly ladder: 'exhausted';
20
26
  };
21
- export type ReattachGiveUp = 'no_run_id' | 'budget_exhausted' | 'bad_input';
27
+ export type ReattachDrainingEvent = Extract<ReattachEvent, {
28
+ readonly cause: 'stream_draining';
29
+ }>;
22
30
  export type ReattachDecision = {
23
31
  readonly action: 'reattach';
24
32
  readonly afterMs: number;
25
33
  readonly budget: ReattachBudget;
26
34
  } | {
27
35
  readonly action: 'give_up';
28
- readonly reason: ReattachGiveUp;
29
36
  readonly budget: ReattachBudget;
37
+ readonly notFollowed: RunNotFollowed<'reattach_exhausted' | 'no_run_id' | 'bad_input'>;
30
38
  };
31
39
  export declare function runReattachPolicy(budget: ReattachBudget, event: ReattachEvent): ReattachDecision;
@@ -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) {
@@ -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') {