@sema-agent/client-core 0.108.0 → 0.109.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 (103) hide show
  1. package/CHANGELOG.md +121 -0
  2. package/README.md +37 -24
  3. package/dist/adapt/arms.js +23 -8
  4. package/dist/adapt/textStream.d.ts +2 -0
  5. package/dist/adapt/textStream.js +37 -16
  6. package/dist/adapter/activeRunSelfHeal.js +1 -1
  7. package/dist/adapter/downstream/eventToSdkMessage.js +2 -1
  8. package/dist/agentSession/engineAgentRegistryRead.js +2 -0
  9. package/dist/bashExitReading.d.ts +12 -0
  10. package/dist/bashExitReading.js +44 -0
  11. package/dist/checkpointRowVerdict.d.ts +1 -1
  12. package/dist/checkpointRowVerdict.js +4 -2
  13. package/dist/compensations.js +1 -1
  14. package/dist/engineAgentAbsence.d.ts +2 -0
  15. package/dist/engineAgentAbsence.js +28 -0
  16. package/dist/engineErrorCodes.d.ts +4 -0
  17. package/dist/engineErrorCodes.js +4 -0
  18. package/dist/engineHealthFacts.d.ts +17 -0
  19. package/dist/engineHealthFacts.js +42 -3
  20. package/dist/engineNoticeCodes.d.ts +128 -0
  21. package/dist/engineNoticeCodes.js +630 -55
  22. package/dist/errorSupplementRoles.js +4 -1
  23. package/dist/fleetTaskDesc.js +3 -3
  24. package/dist/generated/engineFactTables.d.ts +1 -0
  25. package/dist/generated/engineFactTables.js +6 -0
  26. package/dist/generated/engineNoticeTables.js +3 -0
  27. package/dist/hitl/approvalCardReasons.js +22 -4
  28. package/dist/hitl/approvalRequestFrameReading.d.ts +14 -0
  29. package/dist/hitl/approvalRequestFrameReading.js +79 -0
  30. package/dist/hitl/approvalsFeed.js +1 -1
  31. package/dist/hitl/askParkRowRouting.js +5 -2
  32. package/dist/hitl/automaticAnswerVerdict.d.ts +5 -2
  33. package/dist/hitl/automaticAnswerVerdict.js +16 -2
  34. package/dist/hitl/decisionConflict.d.ts +2 -1
  35. package/dist/hitl/decisionConflict.js +4 -1
  36. package/dist/hitl/frameRouter.js +9 -1
  37. package/dist/hitl/hitlBridge.d.ts +4 -0
  38. package/dist/hitl/hitlBridge.js +9 -5
  39. package/dist/hitl/livePendingAsk.js +17 -16
  40. package/dist/hitl/localAllowRule.js +2 -1
  41. package/dist/hitl/noAllowCause.d.ts +1 -1
  42. package/dist/hitl/noAllowCause.js +4 -2
  43. package/dist/hitl/parkExpiryJudgements.js +26 -1
  44. package/dist/hitl/parkOwnership.d.ts +2 -0
  45. package/dist/hitl/parkOwnership.js +48 -8
  46. package/dist/hitl/parkResolver.js +7 -7
  47. package/dist/hitl/parkedUnresumable.d.ts +13 -0
  48. package/dist/hitl/parkedUnresumable.js +80 -0
  49. package/dist/hitl/permissionRuleScope.d.ts +4 -0
  50. package/dist/hitl/permissionRuleScope.js +31 -0
  51. package/dist/hitl/persistedRulesWire.d.ts +2 -0
  52. package/dist/hitl/persistedRulesWire.js +11 -10
  53. package/dist/hitl/sessionAllowRoute.js +5 -4
  54. package/dist/hitl/standingRows.d.ts +1 -5
  55. package/dist/hitl/standingRows.js +15 -14
  56. package/dist/hitl/toolApprovalWire.d.ts +8 -2
  57. package/dist/hitl/toolApprovalWire.js +35 -14
  58. package/dist/hooksWireCaps.js +4 -3
  59. package/dist/hostSettingsSource.js +2 -0
  60. package/dist/i18n/messages.en.d.ts +168 -0
  61. package/dist/i18n/messages.en.js +169 -1
  62. package/dist/i18n/messages.zh.js +169 -1
  63. package/dist/index.d.ts +29 -7
  64. package/dist/index.js +16 -4
  65. package/dist/localEngineJudge.d.ts +75 -0
  66. package/dist/localEngineJudge.js +242 -0
  67. package/dist/localEngineOccupancy.d.ts +88 -0
  68. package/dist/localEngineOccupancy.js +269 -0
  69. package/dist/localEngineSeatReading.d.ts +29 -0
  70. package/dist/localEngineSeatReading.js +294 -0
  71. package/dist/localEngineSpawn.d.ts +113 -0
  72. package/dist/localEngineSpawn.js +486 -0
  73. package/dist/localEngineStep.d.ts +286 -0
  74. package/dist/localEngineStep.js +1086 -0
  75. package/dist/localEngineStopLadder.d.ts +29 -0
  76. package/dist/localEngineStopLadder.js +139 -0
  77. package/dist/localEngineSwapCard.d.ts +43 -0
  78. package/dist/localEngineSwapCard.js +205 -0
  79. package/dist/localEngineTiming.d.ts +8 -0
  80. package/dist/localEngineTiming.js +8 -0
  81. package/dist/mcpReconnect.js +12 -5
  82. package/dist/mcpWireCaps.js +2 -2
  83. package/dist/memoryEntriesWire.d.ts +1 -1
  84. package/dist/memoryEntriesWire.js +5 -0
  85. package/dist/noticePresentation.d.ts +2 -0
  86. package/dist/noticePresentation.js +79 -1
  87. package/dist/ownKey.d.ts +10 -0
  88. package/dist/ownKey.js +15 -1
  89. package/dist/ruleReadings.js +1 -1
  90. package/dist/seam.d.ts +13 -2
  91. package/dist/seam.js +8 -1
  92. package/dist/seatContract.d.ts +42 -4
  93. package/dist/seatContract.js +180 -3
  94. package/dist/sessionBackground.js +2 -0
  95. package/dist/subagentContentStore.d.ts +12 -0
  96. package/dist/subagentContentStore.js +72 -11
  97. package/dist/toolResult.js +18 -5
  98. package/dist/wireFailureShape.d.ts +20 -0
  99. package/dist/wireFailureShape.js +37 -6
  100. package/dist/wireRefusalCopy.d.ts +21 -1
  101. package/dist/wireRefusalCopy.js +91 -2
  102. package/docs/INTEGRATION-CLIENTS.md +1152 -10
  103. package/package.json +3 -3
@@ -3,31 +3,71 @@ import { rowIdTail } from '../workflow.js';
3
3
  import { hostSessionFor } from '../host.js';
4
4
  import { DEFAULT_SESSION_KEY } from '../sessionSlot.js';
5
5
  import { pendingRowIsOfSession } from './parkRowDelegation.js';
6
+ import { slotReadOf } from '../wireFailureShape.js';
6
7
  export function pendingRowIsOwnedByThisSession(row, deps) {
8
+ return ownershipVerdictOf(row, deps) === 'owned';
9
+ }
10
+ export function pendingRowOwnershipOf(row, deps) {
11
+ try {
12
+ return ownershipVerdictOf(row, deps);
13
+ }
14
+ catch {
15
+ return 'unknown';
16
+ }
17
+ }
18
+ function ownershipVerdictOf(row, deps) {
7
19
  const sessionKey = deps?.sessionKey ?? DEFAULT_SESSION_KEY;
8
20
  const rowTaskId = typeof row.taskId === 'string' ? row.taskId : '';
9
21
  if (rowTaskId.length > 0) {
10
22
  const tail = rowIdTail(rowTaskId);
11
23
  if (deps?.isOwnRun !== undefined && deps.isOwnRun(tail))
12
- return true;
24
+ return 'owned';
13
25
  if (sessionKey === DEFAULT_SESSION_KEY && isOwnEngineRun(tail))
14
- return true;
26
+ return 'owned';
15
27
  }
16
28
  const currentSessionId = deps?.currentSessionId ?? (() => hostSessionFor(sessionKey)?.currentSessionId());
17
29
  const sessionId = currentSessionId();
18
30
  if (typeof sessionId !== 'string' || sessionId.length === 0)
31
+ return 'unknown';
32
+ if (pendingRowIsOfSession(row, sessionId))
33
+ return 'owned';
34
+ return rowIsProvablyForeign(row, sessionId) ? 'foreign' : 'unknown';
35
+ }
36
+ function rowIsProvablyForeign(row, selfSessionId) {
37
+ const own = slotReadOf(row, 'sessionId');
38
+ if (own.presence !== 'present' || typeof own.value !== 'string' || own.value === '' || own.value === selfSessionId)
39
+ return false;
40
+ const agent = slotReadOf(row, 'agent');
41
+ if (agent.presence === 'absent')
42
+ return true;
43
+ if (agent.presence !== 'present' || typeof agent.value !== 'object' || agent.value === null)
19
44
  return false;
20
- return pendingRowIsOfSession(row, sessionId);
45
+ for (const key of ['parentSessionId', 'rootSessionId']) {
46
+ const link = slotReadOf(agent.value, key);
47
+ if (link.presence === 'absent')
48
+ continue;
49
+ if (link.presence !== 'present' || typeof link.value !== 'string' || link.value === selfSessionId)
50
+ return false;
51
+ }
52
+ return true;
53
+ }
54
+ export function usableAskQuestions(value) {
55
+ if (!Array.isArray(value) || value.length === 0)
56
+ return null;
57
+ for (let i = 0; i < value.length; i++) {
58
+ if (!Object.hasOwn(value, i))
59
+ return null;
60
+ const q = value[i];
61
+ if (typeof q !== 'object' || q === null || typeof q.question !== 'string')
62
+ return null;
63
+ }
64
+ return value;
21
65
  }
22
66
  export function askQuestionsFromPending(pending) {
23
67
  const input = pending.input;
24
68
  if (typeof input !== 'object' || input === null)
25
69
  return null;
26
- const qs = input.questions;
27
- if (!Array.isArray(qs) || qs.length === 0)
28
- return null;
29
- const ok = qs.every(q => typeof q === 'object' && q !== null && typeof q.question === 'string');
30
- return ok ? qs : null;
70
+ return usableAskQuestions(input.questions);
31
71
  }
32
72
  export function isHitlSafetyErrorLike(e) {
33
73
  return typeof e === 'object' && e !== null && e.name === 'HitlSafetyError';
@@ -10,6 +10,7 @@ import { denyAttributionOf, flushHeldWithInterruptRewrite, isAskTool } from './f
10
10
  import { invokeHostCallbackIsolated } from '../hostCallbackGuard.js';
11
11
  import { inheritFrameBinding } from '../frameBinding.js';
12
12
  import { approvalCallKey, askGateQuestionId } from './gateIdentity.js';
13
+ import { usableAskQuestions } from './parkOwnership.js';
13
14
  import { readDecideReceipt } from '../decideReceipt.js';
14
15
  import { decideFailureNote } from '../decideFailureNote.js';
15
16
  import { wireFailureTextsOf } from '../wireFailureShape.js';
@@ -69,12 +70,11 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
69
70
  return { kind: 'not_sent', lane: 'ask', cause: found.code ?? 'fetch_failed', ...(found.reason !== '' ? { detail: found.reason } : {}), ...(found.rowVerdict !== undefined ? { rowVerdict: found.rowVerdict } : {}) };
70
71
  const { pending, gatedCallId } = found;
71
72
  const at = { lane: 'ask', ...(gatedCallId !== undefined ? { gatedCallId } : {}) };
72
- const fromArgs = gatedCallId
73
- ? askArgsByCall.get(gatedCallId)?.questions
74
- : undefined;
75
- const fromPending = pending.input?.questions;
76
- const questions = Array.isArray(fromArgs) && fromArgs.length > 0 ? fromArgs : fromPending;
77
- if (!Array.isArray(questions) || questions.length === 0) {
73
+ const rowQuestions = usableAskQuestions(pending.input?.questions);
74
+ const streamQuestions = usableAskQuestions(gatedCallId ? askArgsByCall.get(gatedCallId)?.questions : undefined);
75
+ const questionsFromModelCall = rowQuestions === null && streamQuestions !== null;
76
+ const questions = rowQuestions ?? streamQuestions;
77
+ if (questions === null) {
78
78
  const truncated = pending.input?.truncated === true;
79
79
  return {
80
80
  kind: 'not_sent',
@@ -107,7 +107,7 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
107
107
  }
108
108
  signal?.addEventListener('abort', onAbort, { once: true });
109
109
  onPresented?.();
110
- publishQuestionFrame({ type: 'question', questionId, questions });
110
+ publishQuestionFrame({ type: 'question', questionId, questions, ...(questionsFromModelCall ? { _sema_questionsFromModelCall: true } : {}) });
111
111
  });
112
112
  const bridge = new HitlBridge(deps.client, taskId, deps.onDecideOperation ? { onDecideOperation: deps.onDecideOperation } : undefined);
113
113
  if (answer === null) {
@@ -0,0 +1,13 @@
1
+ import { type CheckpointRowBar } from '../checkpointRowVerdict.js';
2
+ import type { UiLanguage } from '../env/uiLanguage.js';
3
+ import type { OwnedPendingListView } from './askParkRowRouting.js';
4
+ import { type ParkOwnershipDeps } from './parkOwnership.js';
5
+ export interface ParkedUnresumableRow {
6
+ readonly rowVerdict: CheckpointRowBar;
7
+ readonly rowKey?: string;
8
+ readonly taskId?: string;
9
+ readonly toolName?: string;
10
+ readonly rowDeadline?: 'armed' | 'none';
11
+ }
12
+ export declare function parkedUnresumableRowsOf(listed: OwnedPendingListView | null | undefined, deps?: ParkOwnershipDeps): readonly ParkedUnresumableRow[];
13
+ export declare function parkedUnresumableLine(row: ParkedUnresumableRow, lang?: UiLanguage | (string & {})): string;
@@ -0,0 +1,80 @@
1
+ import { checkpointDeadlineStateOf, checkpointRowBarOf, checkpointRowBarSentence, rowBarWayOutContent, } from '../checkpointRowVerdict.js';
2
+ import { slotReadOf } from '../wireFailureShape.js';
3
+ import { QUEUE_ROW_FAMILY_UNREADABLE_GATE } from './approvalQueueRow.js';
4
+ import { approvalCallKey } from './gateIdentity.js';
5
+ import { checkpointQueueReadingOf } from './hitlBridge.js';
6
+ import { pendingRowIsOwnedByThisSession } from './parkOwnership.js';
7
+ function ownNonEmpty(o, key) {
8
+ const r = slotReadOf(o, key);
9
+ return r.presence === 'present' && typeof r.value === 'string' && r.value !== '' ? r.value : undefined;
10
+ }
11
+ function memberOf(row, rowVerdict) {
12
+ const taskId = ownNonEmpty(row, 'taskId');
13
+ const callId = ownNonEmpty(row, 'toolCallId') ?? ownNonEmpty(row, 'boundCallId');
14
+ const rowKey = callId !== undefined || taskId !== undefined ? approvalCallKey(callId, taskId ?? '') : undefined;
15
+ const toolName = ownNonEmpty(row, 'toolName');
16
+ const deadline = checkpointDeadlineStateOf(row);
17
+ return Object.freeze({
18
+ rowVerdict,
19
+ ...(rowKey !== undefined ? { rowKey } : {}),
20
+ ...(taskId !== undefined ? { taskId } : {}),
21
+ ...(toolName !== undefined ? { toolName } : {}),
22
+ ...(deadline !== 'unknown' ? { rowDeadline: deadline } : {}),
23
+ });
24
+ }
25
+ export function parkedUnresumableRowsOf(listed, deps) {
26
+ if (typeof listed !== 'object' || listed === null || Array.isArray(listed)) {
27
+ throw new Error('approvals.list answered without a readable body — the rows this engine cannot continue are unknown');
28
+ }
29
+ const pending = listed.pending;
30
+ if (!Array.isArray(pending)) {
31
+ throw new Error('approvals.list answered without a readable `pending` array — the rows this engine cannot continue are unknown');
32
+ }
33
+ const out = [];
34
+ for (const row of pending) {
35
+ const member = parkedMemberOf(row, deps);
36
+ if (member !== undefined)
37
+ out.push(member);
38
+ }
39
+ return Object.freeze(out);
40
+ }
41
+ function parkedMemberOf(row, deps) {
42
+ try {
43
+ if (typeof row !== 'object' || row === null)
44
+ return undefined;
45
+ const reading = checkpointQueueReadingOf(row);
46
+ if (reading === undefined)
47
+ return undefined;
48
+ if (reading.foreign !== null && reading.foreign !== QUEUE_ROW_FAMILY_UNREADABLE_GATE)
49
+ return undefined;
50
+ const bar = checkpointRowBarOf(reading.r);
51
+ if (bar === undefined)
52
+ return undefined;
53
+ if (!pendingRowIsOwnedByThisSession(reading.r, deps))
54
+ return undefined;
55
+ return memberOf(reading.r, bar);
56
+ }
57
+ catch {
58
+ return undefined;
59
+ }
60
+ }
61
+ const UNREADABLE_BAR = Object.freeze({ kind: 'unreadable' });
62
+ export function parkedUnresumableLine(row, lang) {
63
+ let bar = UNREADABLE_BAR;
64
+ let state = 'unknown';
65
+ try {
66
+ if (typeof row === 'object' && row !== null) {
67
+ const v = slotReadOf(row, 'rowVerdict');
68
+ if (v.presence === 'present')
69
+ bar = checkpointRowBarOf({ rowVerdict: v.value }) ?? UNREADABLE_BAR;
70
+ const d = slotReadOf(row, 'rowDeadline');
71
+ if (d.presence === 'present' && (d.value === 'armed' || d.value === 'none'))
72
+ state = d.value;
73
+ }
74
+ }
75
+ catch {
76
+ bar = UNREADABLE_BAR;
77
+ state = 'unknown';
78
+ }
79
+ return `${checkpointRowBarSentence(bar, lang)} · ${rowBarWayOutContent(bar, state, lang)}`;
80
+ }
@@ -0,0 +1,4 @@
1
+ import type { PermissionRuleValue } from '@sema-agent/agent-types';
2
+ export declare function isWholeToolRuleValue(value: PermissionRuleValue): boolean;
3
+ export type AllowRuleScope = 'whole_tool' | 'content' | 'unreadable';
4
+ export declare function allowRuleScopeOf(value: object | null | undefined): AllowRuleScope;
@@ -0,0 +1,31 @@
1
+ import { readOwnDataValue } from '../ownKey.js';
2
+ import { inSlotReadOf } from '../wireFailureShape.js';
3
+ export function isWholeToolRuleValue(value) {
4
+ return value.ruleContent === undefined || value.ruleContent === '';
5
+ }
6
+ export function allowRuleScopeOf(value) {
7
+ try {
8
+ if (typeof value !== 'object' || value === null)
9
+ return 'unreadable';
10
+ const toolName = readOwnDataValue(value, 'toolName');
11
+ if (typeof toolName !== 'string' || toolName === '')
12
+ return 'unreadable';
13
+ let ruleContent;
14
+ const slot = inSlotReadOf(value, 'ruleContent');
15
+ if (slot.state === 'present') {
16
+ const raw = slot.value;
17
+ if (raw !== undefined) {
18
+ if (typeof raw !== 'string' || raw === '')
19
+ return 'unreadable';
20
+ ruleContent = raw;
21
+ }
22
+ }
23
+ else if (slot.state !== 'absent')
24
+ return 'unreadable';
25
+ const snapshot = Object.freeze(Object.assign(Object.create(null), ruleContent !== undefined ? { toolName, ruleContent } : { toolName }));
26
+ return isWholeToolRuleValue(snapshot) ? 'whole_tool' : 'content';
27
+ }
28
+ catch {
29
+ return 'unreadable';
30
+ }
31
+ }
@@ -33,6 +33,8 @@ export interface CcImportRedeemRuleKey {
33
33
  }
34
34
  export interface CcImportRedeemRefused {
35
35
  readonly key?: CcImportRedeemRuleKey;
36
+ readonly reason?: string;
37
+ readonly detail?: string;
36
38
  }
37
39
  export interface CcImportRedeemCounts {
38
40
  persisted: number;
@@ -1,7 +1,7 @@
1
1
  import { displayUntrusted, quoteUntrustedForSentence } from '../displayUntrusted.js';
2
2
  import { engineCapTrue } from '../engineCapsCache.js';
3
3
  import { CORE_RULE_BEHAVIORS } from '../generated/engineFactTables.js';
4
- import { slotReadOf, wireFailureShapeOf } from '../wireFailureShape.js';
4
+ import { inValueOf, slotReadOf, wireFailureShapeOf } from '../wireFailureShape.js';
5
5
  import { CAPABILITY_OPERATOR_PRINCIPALS_REQUIRED } from '../engineErrorCodes.js';
6
6
  import { hostLog } from '../host.js';
7
7
  import { msg, renderMessage } from '../i18n/translate.js';
@@ -51,7 +51,7 @@ function ruleKeyOfRow(row) {
51
51
  const scope = keyObj === undefined ? undefined : ownNonEmpty(keyObj, 'scope');
52
52
  return behavior !== undefined && rule !== undefined && scope !== undefined ? Object.freeze({ behavior, rule, scope }) : undefined;
53
53
  }
54
- function indeterminateRowOf(row) {
54
+ function unsettledRowOf(row) {
55
55
  const key = ruleKeyOfRow(row);
56
56
  const reason = ownNonEmpty(row, 'reason');
57
57
  const d = slotReadOf(row, 'detail');
@@ -62,10 +62,6 @@ function indeterminateRowOf(row) {
62
62
  ...(shown !== undefined && shown.trim() !== '' ? { detail: shown } : {}),
63
63
  });
64
64
  }
65
- function refusedRowOf(row) {
66
- const key = ruleKeyOfRow(row);
67
- return Object.freeze(key !== undefined ? { key } : {});
68
- }
69
65
  const RECEIPT_ROW_ARMS = {
70
66
  persisted: (acc) => {
71
67
  acc.persisted += 1;
@@ -74,10 +70,10 @@ const RECEIPT_ROW_ARMS = {
74
70
  acc.deduped += 1;
75
71
  },
76
72
  refused: (acc, row) => {
77
- appendOwnElement(acc.refused, refusedRowOf(row));
73
+ appendOwnElement(acc.refused, unsettledRowOf(row));
78
74
  },
79
75
  indeterminate: (acc, row) => {
80
- appendOwnElement(acc.indeterminate, indeterminateRowOf(row));
76
+ appendOwnElement(acc.indeterminate, unsettledRowOf(row));
81
77
  },
82
78
  };
83
79
  function receiptCountsOf(rows) {
@@ -333,9 +329,14 @@ export function revokeTargetFromPersistedRule(rule, opts) {
333
329
  };
334
330
  }
335
331
  function readPrincipalOverride(carrier) {
336
- if (carrier === null || typeof carrier !== 'object' || !('principal' in carrier))
332
+ if (carrier === null || typeof carrier !== 'object')
337
333
  return { ok: true };
338
- const raw = carrier.principal;
334
+ const slot = inValueOf(carrier, 'principal');
335
+ if (slot.state === 'absent')
336
+ return { ok: true };
337
+ if (slot.state === 'unproven')
338
+ return { ok: false };
339
+ const raw = slot.value;
339
340
  if (typeof raw !== 'string' || raw.trim().length === 0)
340
341
  return { ok: false };
341
342
  return { ok: true, principal: raw };
@@ -1,3 +1,4 @@
1
+ import { inSlotReadOf } from '../wireFailureShape.js';
1
2
  export function sessionAllowCarried(leg, approvalId) {
2
3
  if (leg === 'frame')
3
4
  return true;
@@ -7,10 +8,10 @@ export function sessionAllowCarried(leg, approvalId) {
7
8
  }
8
9
  export function readCardUnreadableEvidence(card) {
9
10
  try {
10
- if (!('unreadableEvidence' in card))
11
+ const slot = inSlotReadOf(card, 'unreadableEvidence');
12
+ if (slot.state === 'absent')
11
13
  return 'absent';
12
- const d = Object.getOwnPropertyDescriptor(card, 'unreadableEvidence');
13
- const list = d !== undefined && Object.hasOwn(d, 'value') ? d.value : undefined;
14
+ const list = slot.state === 'present' ? slot.value : undefined;
14
15
  return Array.isArray(list) && list.every((k) => typeof k === 'string') ? list : 'unreadable';
15
16
  }
16
17
  catch {
@@ -21,7 +22,7 @@ export function sessionAllowCardReadable(card) {
21
22
  if (typeof card !== 'object' || card === null)
22
23
  return 'card_unreadable';
23
24
  try {
24
- if ('cardPayloadUnreadable' in card)
25
+ if (inSlotReadOf(card, 'cardPayloadUnreadable').state !== 'absent')
25
26
  return 'card_unreadable';
26
27
  }
27
28
  catch {
@@ -25,13 +25,9 @@ export interface RememberOfferable {
25
25
  readonly deploymentList: RememberOffer;
26
26
  }
27
27
  export declare function rememberOfferable(card: object | null | undefined, host?: StandingRowsHostFacts | null): RememberOfferable;
28
- export interface LocalAllowRuleHostFacts {
29
- readonly hostLadderEnforces?: boolean;
30
- }
31
28
  export interface LocalAllowRuleEnforcement {
32
29
  readonly enforceable: boolean;
33
- readonly by?: 'host_ladder' | 'engine_grant';
34
30
  readonly engine: 'sent' | 'kept' | 'unreadable';
35
31
  readonly keptReason?: PermissionsKeptReason;
36
32
  }
37
- export declare function localAllowRuleEnforceable(rule: string, caps: PermissionsAllowGrantReading, host?: LocalAllowRuleHostFacts | null): LocalAllowRuleEnforcement;
33
+ export declare function localAllowRuleEnforceable(rule: string, caps: PermissionsAllowGrantReading): LocalAllowRuleEnforcement;
@@ -1,7 +1,8 @@
1
1
  import { DEPLOYMENT_LIST_ASK_CLASSES, readAskClassPairState } from './askClassPair.js';
2
- import { automaticAnswerVerdict } from './automaticAnswerVerdict.js';
2
+ import { askLevelVerdict } from './automaticAnswerVerdict.js';
3
3
  import { readApprovalCardWord, readRuleOffers } from './toolApprovalWire.js';
4
4
  import { CORE_SKILL_TOOL_NAME } from '../generated/toolNameTables.js';
5
+ import { inSlotReadOf, inValueOf } from '../wireFailureShape.js';
5
6
  import { toolPermissionRequestIdDomain } from '../seatContract.js';
6
7
  import { permissionsDeclarationReport } from '../permissionsDeclaration.js';
7
8
  import { readCardUnreadableEvidence, sessionAllowCardReadable, sessionAllowEligible } from './sessionAllowRoute.js';
@@ -15,9 +16,10 @@ function ownData(v, key) {
15
16
  return undefined;
16
17
  }
17
18
  }
18
- function hasKey(v, key) {
19
+ function candidateSlot(v, key) {
19
20
  try {
20
- return { inCard: key in v };
21
+ const s = inSlotReadOf(v, key);
22
+ return s.state === 'unproven' ? UNREADABLE : s;
21
23
  }
22
24
  catch {
23
25
  return UNREADABLE;
@@ -25,8 +27,10 @@ function hasKey(v, key) {
25
27
  }
26
28
  function present(v, key) {
27
29
  try {
28
- const inCard = key in v;
29
- return { inCard, value: inCard ? v[key] : undefined };
30
+ const s = inValueOf(v, key);
31
+ if (s.state === 'unproven')
32
+ return UNREADABLE;
33
+ return s.state === 'present' ? { inCard: true, value: s.value } : { inCard: false, value: undefined };
30
34
  }
31
35
  catch {
32
36
  return UNREADABLE;
@@ -42,12 +46,12 @@ function persistentRowOf(card, host, evidenceUnreadable) {
42
46
  if (evidenceUnreadable)
43
47
  return { row: 'none', why: 'evidence_unreadable' };
44
48
  for (const key of ['ruleOffers', 'ruleOffersReadOnly']) {
45
- const p = hasKey(card, key);
49
+ const p = candidateSlot(card, key);
46
50
  if (p === UNREADABLE)
47
51
  return UNREADABLE;
48
- if (!p.inCard)
52
+ if (p.state === 'absent')
49
53
  continue;
50
- const offers = readRuleOffers(ownData(card, key));
54
+ const offers = readRuleOffers(p.state === 'present' ? p.value : undefined);
51
55
  if (offers === undefined || offers.length === 0)
52
56
  return { row: 'none', why: 'offers_unreadable' };
53
57
  return { row: 'engine_offer', why: 'given' };
@@ -69,7 +73,7 @@ function persistentRowOf(card, host, evidenceUnreadable) {
69
73
  return { row: 'none', why: 'tool_name_unreadable' };
70
74
  if (tool === CORE_SKILL_TOOL_NAME)
71
75
  return { row: 'none', why: 'grant_binding' };
72
- if (automaticAnswerVerdict(card, 'standing_rule').kind !== 'may_answer')
76
+ if (askLevelVerdict(card, 'standing_rule').kind !== 'may_answer')
73
77
  return { row: 'none', why: 'rule_cannot_clear' };
74
78
  const face = readApprovalCardWord(card);
75
79
  if (face === undefined)
@@ -178,8 +182,7 @@ export function rememberOfferable(card, host) {
178
182
  deploymentList: SESSION_OFFER[rows.why.deploymentList],
179
183
  });
180
184
  }
181
- export function localAllowRuleEnforceable(rule, caps, host) {
182
- const ladder = typeof host === 'object' && host !== null && ownData(host, 'hostLadderEnforces') === true;
185
+ export function localAllowRuleEnforceable(rule, caps) {
183
186
  let engine = 'unreadable';
184
187
  let keptReason;
185
188
  if (typeof rule === 'string' && rule.trim() !== '') {
@@ -199,10 +202,8 @@ export function localAllowRuleEnforceable(rule, caps, host) {
199
202
  engine = 'unreadable';
200
203
  }
201
204
  }
202
- const by = ladder ? 'host_ladder' : engine === 'sent' ? 'engine_grant' : undefined;
203
205
  return Object.freeze({
204
- enforceable: by !== undefined,
205
- ...(by !== undefined ? { by } : {}),
206
+ enforceable: engine === 'sent',
206
207
  engine,
207
208
  ...(keptReason !== undefined ? { keptReason } : {}),
208
209
  });
@@ -12,6 +12,7 @@ import { type ApprovalMandateWord } from '../gateVocabulary.js';
12
12
  import type { ToolPermissionRequest } from '../seatContract.js';
13
13
  import { type ApprovalDecidedByReading } from '../approvalDecidedByCapability.js';
14
14
  import { type PolicyDenyCause } from './policyDenyCause.js';
15
+ import { type ApprovalRequestFrameReading, type ApprovalRequestFrameUnreadableWhy } from './approvalRequestFrameReading.js';
15
16
  import { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS, RULE_OFFERS_ABSENCE_REASONS } from '@sema-agent/sdk';
16
17
  import type { ApprovalDecidedBy, ApprovalCardDelegation, ToolApprovalDecision as SdkToolApprovalDecision, ToolApprovalRespondAck, ToolApprovalRespondReplayed, PersistedRuleAnchor, RuleOfferMatch, RuleOfferBatchMember, RuleOfferUncoveredDetail, RuleOfferUncoveredReason, AskOrigin, RuleOffersAbsence, DenialLimitKind, DenialLimitFallback, ToolApprovalFrame, ApprovalRequestFrame, RuleOffer as SdkRuleOffer } from '@sema-agent/sdk';
17
18
  export declare function toolNameIsFsWrite(name: string): boolean;
@@ -138,6 +139,7 @@ export interface ApprovalCardRequest {
138
139
  priorCardsForCall?: readonly ApprovalPriorCard[];
139
140
  message?: string;
140
141
  cardPayloadUnreadable?: true;
142
+ cardPayloadUnreadableWhy?: ApprovalRequestFrameUnreadableWhy;
141
143
  supersedes?: ApprovalCardSupersedes;
142
144
  parkRow?: ApprovalCardParkRow;
143
145
  }
@@ -283,7 +285,7 @@ export declare function readDenialLimitFallback(v: unknown): DenialLimitFallback
283
285
  export declare function readRuleOffers(v: unknown): RuleOfferReading[] | undefined;
284
286
  export declare function readRuleOfferSupply(offers: unknown): RuleOfferReading[] | undefined;
285
287
  export type ApprovalCardV1Facts = Pick<ApprovalCardRequest, 'toolName' | 'args'> & Pick<ApprovalCardRequest, 'toolCallId' | 'workerBadge' | 'wireNote' | 'argsUnavailable' | 'governanceForced' | 'delegation' | 'ruleOffersReadOnly' | 'inputHasBidi' | 'allowRuleShadow' | 'probeCause' | 'rememberRefused' | 'ruleEvidence' | 'requiresRealApproval' | 'ruleOffersAbsence' | 'denialLimitFallback' | 'askClass' | 'classes' | 'agentId' | 'askReasons' | 'grantBinding' | 'unreadableEvidence' | 'message' | 'approvalCard'>;
286
- export type ApprovalCardFacts = ApprovalCardV1Facts & Pick<ApprovalCardRequest, 'argsRewritten' | 'ruleOffers' | 'probeReason' | 'mandated' | 'mandate' | 'readRootCandidate' | 'origin' | 'boundGrants' | 'classifierNoRuling' | 'allowSessionCarried' | 'cardPayloadUnreadable'>;
288
+ export type ApprovalCardFacts = ApprovalCardV1Facts & Pick<ApprovalCardRequest, 'argsRewritten' | 'ruleOffers' | 'probeReason' | 'mandated' | 'mandate' | 'readRootCandidate' | 'origin' | 'boundGrants' | 'classifierNoRuling' | 'allowSessionCarried' | 'cardPayloadUnreadable' | 'cardPayloadUnreadableWhy'>;
287
289
  export interface ApprovalCardFrameFactsOptions {
288
290
  readonly streamArgs?: object;
289
291
  readonly argsUnavailable?: true;
@@ -291,7 +293,7 @@ export interface ApprovalCardFrameFactsOptions {
291
293
  export declare function approvalCardFactsOfV1(card: object | null | undefined): Readonly<ApprovalCardV1Facts> | undefined;
292
294
  export declare function approvalCardFactsOfV1Frame(frame: object | null | undefined): Readonly<ApprovalCardFacts> | undefined;
293
295
  export declare function approvalCardFactsOfFrame(frame: object | null | undefined, opts?: ApprovalCardFrameFactsOptions | null): Readonly<ApprovalCardFacts> | undefined;
294
- export declare function approvalCardFactsOfGeneric(card: object | null | undefined): Readonly<ApprovalCardFacts> | undefined;
296
+ export declare function approvalCardFactsOfGeneric(card: object | null | undefined, reading?: ApprovalRequestFrameReading | null): Readonly<ApprovalCardFacts> | undefined;
295
297
  export interface ToolApprovalFrameLaneOpts {
296
298
  approvalDecisionNoteCapable?: boolean;
297
299
  respondFreeFormRulesCapable?: boolean;
@@ -304,6 +306,10 @@ export declare function surfaceToolApprovalFrameAndRespond(frame: ToolApprovalFr
304
306
  readonly taskId?: string;
305
307
  readonly sessionId?: string;
306
308
  }): Promise<ApprovalResolution>;
309
+ export declare function surfaceApprovalFrameWithId(frame: ToolApprovalFrame | ApprovalRequestFrame, approvalId: string, respond: RespondToolApprovalFn, streamArgs: Parameters<typeof surfaceToolApprovalFrameAndRespond>[2], signal: AbortSignal | undefined, lane: ToolApprovalFrameLaneOpts | undefined, run: {
310
+ readonly taskId?: string;
311
+ readonly sessionId?: string;
312
+ } | undefined): Promise<ApprovalResolution>;
307
313
  export interface ApprovalHolderIdentity {
308
314
  readonly approvalId?: string;
309
315
  readonly taskId?: string;
@@ -30,13 +30,14 @@ import { isApprovalMandateWord } from '../gateVocabulary.js';
30
30
  import { approvalDecidedByAdmitted } from '../approvalDecidedByCapability.js';
31
31
  import { decideFailureNote } from '../decideFailureNote.js';
32
32
  import { readPolicyDenyCause } from './policyDenyCause.js';
33
- import { slotPresenceOf, wireFailureTextsOf } from '../wireFailureShape.js';
33
+ import { inValueOf, slotPresenceOf, wireFailureTextsOf } from '../wireFailureShape.js';
34
34
  import { APPROVAL_LIVE_FACE_KEYS, approvalLiveFaceOf, hasOwnStrictTrue } from './approvalLiveFace.js';
35
+ import { approvalFrameUnreadableWhyOf, approvalRequestFrameReadingThrowing, } from './approvalRequestFrameReading.js';
35
36
  import { subagentSourceSessionOf } from './subagentSource.js';
36
37
  import { isPlainRecord } from '../jsonFaithful.js';
37
38
  import { DECIDE_WORKFLOW_REMEMBER_UNSUPPORTED } from '../engineErrorCodes.js';
38
39
  import { readDecideReceipt } from '../decideReceipt.js';
39
- import { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS, RULE_OFFERS_ABSENCE_REASONS, ASK_ORIGINS, isApprovalRequestFrameV1, } from '@sema-agent/sdk';
40
+ import { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS, RULE_OFFERS_ABSENCE_REASONS, ASK_ORIGINS, } from '@sema-agent/sdk';
40
41
  export function toolNameIsFsWrite(name) {
41
42
  const n = name.replace(/[\s_-]+/g, '').toLowerCase();
42
43
  return (n === 'write' || n === 'filewrite' || n === 'edit' || n === 'fileedit' || n === 'multiedit' || n === 'notebookedit');
@@ -493,6 +494,7 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
493
494
  const rowArgsGap = reading.from !== 'none' ? undefined : reading.shape === 'absent' ? 'absent' : reading.shape === 'size_marker' ? 'truncated' : undefined;
494
495
  const args = reading.from !== 'none' ? reading.args : rowArgsGap !== undefined ? {} : reading.recorded;
495
496
  const argsRewritten = reading.from === 'recorded' && reading.rewritten;
497
+ const argsFromModelCall = reading.from === 'stream';
496
498
  if (typeof args !== 'object' || args === null) {
497
499
  return { kind: 'not_sent', ...at, cause: 'no_input', detail: 'gate has no tool input payload' };
498
500
  }
@@ -516,6 +518,7 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
516
518
  }
517
519
  : {}),
518
520
  ...(argsRewritten ? { argsRewritten: true, wireNote: ARGS_REWRITTEN_NOTE } : {}),
521
+ ...(argsFromModelCall ? { argsUnavailable: true, wireNote: renderMessage(msg('approval.park.argsFromModelCall')) } : {}),
519
522
  ...(pending.governanceForced === true ? { governanceForced: true } : {}),
520
523
  ...(readRequiresRealApproval(pending) ? { requiresRealApproval: true } : {}),
521
524
  ...approvalLiveFaceOf(pending).facts,
@@ -541,8 +544,8 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
541
544
  });
542
545
  const card = await surfaceCallTrackedCard(gatedCallId, undefined, ledgerSlot, cardRequest);
543
546
  hold.phase = 'respond';
544
- if (rowArgsGap !== undefined && card.kind === 'allow' && card.updatedInput !== undefined) {
545
- hostLog('error', `liveToolApprovalWire: approval card for ${gatedCallId ?? `task ${taskId}`} returned an edited approval, but the tool input is ${rowArgsGap === 'truncated' ? 'a size marker on the pending row' : 'missing from both the stream and the pending row'} — nothing sent (the park stays pending)`);
547
+ if ((rowArgsGap !== undefined || argsFromModelCall) && card.kind === 'allow' && card.updatedInput !== undefined) {
548
+ hostLog('error', `liveToolApprovalWire: approval card for ${gatedCallId ?? `task ${taskId}`} returned an edited approval, but the tool input is ${rowArgsGap === 'truncated' ? 'a size marker on the pending row' : rowArgsGap === 'absent' ? 'missing from both the stream and the pending row' : 'not recorded on the pending row'} — nothing sent (the park stays pending)`);
546
549
  surfaceEditRefusedOnBlindAsk();
547
550
  return { kind: 'not_sent', ...at, cause: 'edit_refused', detail: PARK_EDIT_REFUSED_REASON };
548
551
  }
@@ -685,8 +688,11 @@ function readReadRootCandidate(v) {
685
688
  const o = v;
686
689
  if (typeof o.dir !== 'string' || o.dir.length === 0 || o.clearsThisAsk !== true)
687
690
  return undefined;
688
- if ('covers' in o)
689
- return o.covers === 'exact' ? { dir: o.dir, clearsThisAsk: true, covers: 'exact' } : undefined;
691
+ const covers = inValueOf(o, 'covers');
692
+ if (covers.state === 'unproven')
693
+ return undefined;
694
+ if (covers.state === 'present')
695
+ return covers.value === 'exact' ? { dir: o.dir, clearsThisAsk: true, covers: 'exact' } : undefined;
690
696
  return { dir: o.dir, clearsThisAsk: true };
691
697
  }
692
698
  export const TOOL_APPROVAL_FRAME_KEYS_MIRROR = [
@@ -1679,7 +1685,7 @@ function v1Bit(stamp) {
1679
1685
  const SUSPENDED_ASK_ARGS_NOTE = "this request's tool arguments are not available on this surface — it was raised while no client was connected, " +
1680
1686
  'so you are deciding without seeing them; deny it if you are not sure what it will do. ' +
1681
1687
  'Edits are not accepted on this card: there is no original input to edit';
1682
- function cardFactsOf(carrier, leg, opts, liveFacts, approvalId) {
1688
+ function cardFactsOf(carrier, leg, opts, liveFacts, approvalId, payloadWhy) {
1683
1689
  const c = carrier;
1684
1690
  const full = leg !== 'generic';
1685
1691
  const live = leg === 'frame' ? carrier : leg === 'v1' ? liveFacts : undefined;
@@ -1865,8 +1871,11 @@ function cardFactsOf(carrier, leg, opts, liveFacts, approvalId) {
1865
1871
  if (typeof msg === 'string' && msg.trim() !== '')
1866
1872
  out.message = msg;
1867
1873
  });
1868
- if (leg === 'generic')
1874
+ if (leg === 'generic') {
1869
1875
  out.cardPayloadUnreadable = true;
1876
+ if (payloadWhy !== undefined)
1877
+ out.cardPayloadUnreadableWhy = payloadWhy;
1878
+ }
1870
1879
  if (sessionAllowCarried(leg, approvalId))
1871
1880
  out.allowSessionCarried = true;
1872
1881
  out.grantBinding = readApprovalGrantBinding(carrier);
@@ -1898,15 +1907,26 @@ export function approvalCardFactsOfFrame(frame, opts) {
1898
1907
  return undefined;
1899
1908
  return Object.freeze(cardFactsOf(frame, 'frame', opts));
1900
1909
  }
1901
- export function approvalCardFactsOfGeneric(card) {
1910
+ export function approvalCardFactsOfGeneric(card, reading) {
1902
1911
  if (typeof card !== 'object' || card === null)
1903
1912
  return undefined;
1904
- return Object.freeze(cardFactsOf(card, 'generic'));
1913
+ return Object.freeze(cardFactsOf(card, 'generic', null, undefined, undefined, approvalFrameUnreadableWhyOf(reading)));
1905
1914
  }
1906
1915
  const MAX_RESPOND_NOTE_CHARS = 2048;
1907
1916
  export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamArgs, signal, lane, run) {
1917
+ const approvalId = readOwnDataNonEmptyString(frame, 'approvalId');
1918
+ if (approvalId === undefined)
1919
+ return approvalIdUnreadableRefusal();
1920
+ return surfaceApprovalFrameWithId(frame, approvalId, respond, streamArgs, signal, lane, run);
1921
+ }
1922
+ function approvalIdUnreadableRefusal() {
1923
+ hostLog('debug', 'liveToolApprovalWire: refused (approvalId_unreadable)');
1924
+ return { kind: 'not_sent', lane: 'respond', cause: 'card_unavailable' };
1925
+ }
1926
+ export async function surfaceApprovalFrameWithId(frame, approvalId, respond, streamArgs, signal, lane, run) {
1908
1927
  const receivedAtMs = Date.now();
1909
- const approvalId = frame.approvalId;
1928
+ if (typeof approvalId !== 'string' || approvalId === '')
1929
+ return approvalIdUnreadableRefusal();
1910
1930
  const carriers = openingFrameCarriersOf(frame);
1911
1931
  const entry = { frame, carriers, approvalId, identity: liveCardIdentityOf(approvalId, frame, carriers, run), respond, streamArgs, signal, lane, receivedAtMs };
1912
1932
  return enterLiveCard(entry);
@@ -1916,9 +1936,10 @@ function openingFrameCarriersOf(frame) {
1916
1936
  return { body: frame, live: frame, leg: 'frame' };
1917
1937
  const card = frame.card;
1918
1938
  const pinned = Object.create(frame, { card: { value: card, enumerable: true } });
1919
- if (isApprovalRequestFrameV1(pinned))
1939
+ const reading = approvalRequestFrameReadingThrowing(pinned);
1940
+ if (reading.v1)
1920
1941
  return { body: pinned.card, live: frame, leg: 'v1' };
1921
- return { body: typeof card === 'object' && card !== null ? card : {}, live: undefined, leg: 'generic' };
1942
+ return { body: typeof card === 'object' && card !== null ? card : {}, live: undefined, leg: 'generic', why: reading.why };
1922
1943
  }
1923
1944
  function enterLiveCard(e) {
1924
1945
  const { approvalId, signal } = e;
@@ -2016,7 +2037,7 @@ async function presentToolApprovalFrameAndRespond(frame, carriers, approvalId, r
2016
2037
  const bodyToolCallId = carriers.body.toolCallId;
2017
2038
  const frameToolCallId = typeof bodyToolCallId === 'string' && bodyToolCallId !== '' ? bodyToolCallId : undefined;
2018
2039
  const decidedByReading = decidedByReadingSnapshotOf(lane);
2019
- const facts = cardFactsOf(carriers.body, carriers.leg, { streamArgs, ...(lane?.argsUnavailable === true ? { argsUnavailable: true } : {}) }, carriers.live, approvalId);
2040
+ const facts = cardFactsOf(carriers.body, carriers.leg, { streamArgs, ...(lane?.argsUnavailable === true ? { argsUnavailable: true } : {}) }, carriers.live, approvalId, carriers.why);
2020
2041
  const argsNotRealInput = facts.argsUnavailable === true;
2021
2042
  const ruleOffers = facts.ruleOffers;
2022
2043
  if (argsNotRealInput && lane?.argsUnavailable !== true && facts.wireNote !== undefined) {
@@ -1,7 +1,7 @@
1
1
  import { engineCapNestedTrue, engineCapState, engineCapString, engineCapValue } from './engineCapsCache.js';
2
2
  import { hooksReaderNotReadKeys } from './hooksReaderGenerations.js';
3
3
  import { msg, renderMessage } from './i18n/translate.js';
4
- import { capForDisplay, collapseLabel } from './fleetTaskDesc.js';
4
+ import { capForDisplay } from './fleetTaskDesc.js';
5
5
  import { hostLog } from './host.js';
6
6
  import { faultText } from './hostCallbackGuard.js';
7
7
  import { HostSettingsSourceShapeError, MANAGED_READING_DISPOSITIONS, mergedBooleanOf, nonManagedLayerDown, readHostSettingsOnce } from './hostSettingsSource.js';
@@ -555,8 +555,9 @@ export function fleetHookFailureNoticeCapable(baseUrl) {
555
555
  export const HOOK_FAILURE_WARN_PREFIX = 'Hook error';
556
556
  export function hookFailureNoticeText(n) {
557
557
  const code = n.exitCode !== undefined ? `exit ${n.exitCode}` : n.reason;
558
- const name = collapseLabel(n.hookName);
559
- const tail = n.stderr !== undefined ? `: ${collapseLabel(n.stderr)}` : '';
558
+ const fold = (s) => s.replace(/\s+/g, ' ').trim();
559
+ const name = fold(n.hookName);
560
+ const tail = n.stderr !== undefined ? `: ${fold(n.stderr)}` : '';
560
561
  const line = `${HOOK_FAILURE_WARN_PREFIX}: ${name} (${code})${tail}`;
561
562
  return capForDisplay(line, MAX_HOOK_NOTICE_TEXT_CHARS);
562
563
  }