@sema-agent/client-core 0.105.0 → 0.106.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 (132) hide show
  1. package/CHANGELOG.md +220 -0
  2. package/README.md +26 -11
  3. package/dist/adapt/arms.js +10 -1
  4. package/dist/adapter/activeRunSelfHeal.d.ts +13 -2
  5. package/dist/adapter/activeRunSelfHeal.js +80 -7
  6. package/dist/adapter/downstream/eventToSdkMessage.d.ts +0 -5
  7. package/dist/adapter/downstream/eventToSdkMessage.js +20 -19
  8. package/dist/adapter/downstream/terminalToSdkResult.js +2 -3
  9. package/dist/adapter/downstream/wiringManifestView.d.ts +1 -3
  10. package/dist/adapter/downstream/wiringManifestView.js +0 -1
  11. package/dist/adapter/runStream.js +3 -4
  12. package/dist/approvalReachWire.d.ts +1 -0
  13. package/dist/approvalReachWire.js +4 -1
  14. package/dist/approvalSeat.d.ts +21 -0
  15. package/dist/approvalSeat.js +73 -0
  16. package/dist/assistantInbox.d.ts +2 -0
  17. package/dist/assistantInbox.js +3 -0
  18. package/dist/checkpointRowVerdict.d.ts +23 -0
  19. package/dist/checkpointRowVerdict.js +89 -0
  20. package/dist/coreValuePorts.d.ts +12 -1
  21. package/dist/coreValuePorts.js +10 -0
  22. package/dist/decideFailureNote.js +3 -1
  23. package/dist/decideReceipt.d.ts +9 -0
  24. package/dist/decideReceipt.js +29 -1
  25. package/dist/declared/supportFloorDeclared.js +2 -2
  26. package/dist/deviceRebind.d.ts +7 -0
  27. package/dist/deviceRebind.js +39 -5
  28. package/dist/engineAddress.d.ts +3 -0
  29. package/dist/engineAddress.js +25 -0
  30. package/dist/engineDirectory.js +9 -17
  31. package/dist/engineErrorCodes.d.ts +8 -0
  32. package/dist/engineErrorCodes.js +8 -0
  33. package/dist/engineNoticeCodes.d.ts +26 -3
  34. package/dist/engineNoticeCodes.js +52 -7
  35. package/dist/engineRowRef.js +1 -1
  36. package/dist/errorSupplementRoles.js +2 -0
  37. package/dist/eventLeg.d.ts +2 -2
  38. package/dist/eventLeg.js +2 -1
  39. package/dist/fleet/fleetProjection.js +26 -5
  40. package/dist/generated/engineFactTables.d.ts +2 -1
  41. package/dist/generated/engineFactTables.js +3 -1
  42. package/dist/generated/engineNoticeTables.js +19 -4
  43. package/dist/generated/sdkFactTables.d.ts +3 -0
  44. package/dist/generated/sdkFactTables.js +15 -0
  45. package/dist/generated/settingsSchemaFns.d.ts +11 -0
  46. package/dist/generated/settingsSchemaFns.js +2 -1
  47. package/dist/generated/settingsSchemaSafetyMergeSpec.js +1 -1
  48. package/dist/generated/settingsSchemaSettingsSources.d.ts +1 -1
  49. package/dist/generated/settingsSchemaSettingsSources.js +22 -2
  50. package/dist/hitl/approvalLiveFace.d.ts +17 -0
  51. package/dist/hitl/approvalLiveFace.js +35 -0
  52. package/dist/hitl/approvalQueueRow.d.ts +21 -0
  53. package/dist/hitl/approvalQueueRow.js +69 -0
  54. package/dist/hitl/approvalsFeed.d.ts +5 -4
  55. package/dist/hitl/approvalsFeed.js +38 -6
  56. package/dist/hitl/askParkRowRouting.d.ts +4 -3
  57. package/dist/hitl/askParkRowRouting.js +15 -3
  58. package/dist/hitl/automaticAnswerVerdict.js +13 -10
  59. package/dist/hitl/crashConverged.d.ts +5 -1
  60. package/dist/hitl/decisionConflict.js +2 -1
  61. package/dist/hitl/decisionRefusal.js +1 -1
  62. package/dist/hitl/decisionSend.d.ts +1 -0
  63. package/dist/hitl/decisionSend.js +14 -2
  64. package/dist/hitl/frameRouter.js +4 -5
  65. package/dist/hitl/hitlBridge.d.ts +1 -1
  66. package/dist/hitl/hitlBridge.js +26 -5
  67. package/dist/hitl/livePendingAsk.d.ts +5 -0
  68. package/dist/hitl/livePendingAsk.js +6 -1
  69. package/dist/hitl/planReviewCard.d.ts +2 -0
  70. package/dist/hitl/planReviewCard.js +21 -0
  71. package/dist/hitl/planReviewWire.d.ts +2 -2
  72. package/dist/hitl/planReviewWire.js +101 -36
  73. package/dist/hitl/sessionAllowRoute.d.ts +2 -0
  74. package/dist/hitl/sessionAllowRoute.js +7 -0
  75. package/dist/hitl/sessionPolicyWire.d.ts +1 -7
  76. package/dist/hitl/sessionPolicyWire.js +3 -43
  77. package/dist/hitl/subagentSource.d.ts +1 -0
  78. package/dist/hitl/subagentSource.js +13 -0
  79. package/dist/hitl/toolApprovalWire.d.ts +3 -2
  80. package/dist/hitl/toolApprovalWire.js +79 -37
  81. package/dist/hostSettingsSource.d.ts +4 -1
  82. package/dist/hostSettingsSource.js +11 -6
  83. package/dist/hostTurnSettings.d.ts +4 -5
  84. package/dist/hostTurnSettings.js +80 -19
  85. package/dist/i18n/messages.en.d.ts +35 -12
  86. package/dist/i18n/messages.en.js +36 -13
  87. package/dist/i18n/messages.zh.js +38 -13
  88. package/dist/index.d.ts +21 -2
  89. package/dist/index.js +11 -2
  90. package/dist/liveInitToolFace.js +0 -1
  91. package/dist/liveModelCatalog.d.ts +15 -0
  92. package/dist/liveModelCatalog.js +40 -0
  93. package/dist/mcpWireCaps.js +1 -1
  94. package/dist/modelCapabilityProbe.js +66 -33
  95. package/dist/ownKey.d.ts +0 -1
  96. package/dist/ownKey.js +0 -2
  97. package/dist/permissionsDeclaration.d.ts +2 -2
  98. package/dist/permissionsDeclaration.js +12 -14
  99. package/dist/projectSeed.d.ts +39 -0
  100. package/dist/projectSeed.js +80 -0
  101. package/dist/promptDelivery.js +1 -1
  102. package/dist/replayBody.d.ts +7 -6
  103. package/dist/replayBody.js +62 -7
  104. package/dist/request/taskRequest.js +3 -4
  105. package/dist/ruleCompileVerdict.d.ts +0 -2
  106. package/dist/ruleCompileVerdict.js +0 -31
  107. package/dist/ruleRefusalSentence.js +0 -3
  108. package/dist/runReattachPolicy.d.ts +3 -0
  109. package/dist/runReattachPolicy.js +15 -2
  110. package/dist/runTerminal.js +30 -8
  111. package/dist/sandboxWire.d.ts +0 -3
  112. package/dist/sandboxWire.js +0 -9
  113. package/dist/seam.d.ts +14 -7
  114. package/dist/seam.js +12 -0
  115. package/dist/seatContract.d.ts +57 -7
  116. package/dist/seatContract.js +132 -3
  117. package/dist/serverSupportRange.d.ts +18 -0
  118. package/dist/serverSupportRange.js +25 -0
  119. package/dist/sessionEngine.d.ts +156 -0
  120. package/dist/sessionEngine.js +464 -0
  121. package/dist/sessionMap.d.ts +11 -1
  122. package/dist/sessionMap.js +35 -9
  123. package/dist/sessionMemorySync.d.ts +31 -0
  124. package/dist/sessionMemorySync.js +95 -0
  125. package/dist/sessionRowTag.d.ts +4 -0
  126. package/dist/sessionRowTag.js +18 -0
  127. package/dist/settingsLayerAdmission.d.ts +1 -0
  128. package/dist/settingsLayerAdmission.js +4 -1
  129. package/dist/toolResult.js +1 -1
  130. package/dist/wireRefusalCopy.js +2 -6
  131. package/docs/INTEGRATION-CLIENTS.md +1022 -16
  132. package/package.json +5 -5
@@ -2,22 +2,21 @@ import { publishQuestionFrame, publishQuestionFrameFor, registerLocalQuestionRes
2
2
  import { hostLog } from '../host.js';
3
3
  import { abortableSleep, settleWithinDeadline } from '../abortableSleep.js';
4
4
  import { clientHasVerbs, engineWireFor, noAnswerKindOf, sendOnce } from '../engineWireFor.js';
5
- import { engineCapTrue } from '../engineCapsCache.js';
6
5
  import { engineWireTarget } from '../engineWireTarget.js';
7
6
  import { enqueuePlanReviewOutcome } from '../notifications.js';
8
7
  import { corruptStoredRowContent, corruptStoredRowWhere } from '../wireErrorTriage.js';
9
8
  import { decideFailureNote } from '../decideFailureNote.js';
10
- import { wireFailureTextsOf } from '../wireFailureShape.js';
9
+ import { readThrownExtra, wireFailureTextsOf } from '../wireFailureShape.js';
11
10
  import { appendOwnElement, readOwnDataNonEmptyString, readOwnDataValue } from '../ownKey.js';
12
11
  import { checkpointUnsupportedVersionContent, checkpointUnsupportedVersionFromError, resumeContextUnavailableContent, resumeContextUnavailableFromError } from '../resumeRefusalCopy.js';
13
- import { REQUEST_FIELD_CONFLICT } from '../engineErrorCodes.js';
12
+ import { APPROVAL_STALE, REQUEST_FIELD_CONFLICT } from '../engineErrorCodes.js';
14
13
  import { refusalDeliveryOf } from './decisionRefusal.js';
15
14
  import { directFailureVerdictOf, directSafetyStopOf } from './approvalResolution.js';
16
15
  import { __resetDecisionLedgerForTests, decisionInFlightOf, decisionKeyInFlight, decisionLedgerKeysOf, decisionOpsWithKey, decisionReadMark, dropDecisionOp, latestDecisionHandedMark, markLatestDecisionNotApplied, openDecisionOp, planDecisionKey, readMarkOf, settleDecisionOp, settleDecisionOpsOnGateLeft, } from './decisionLedger.js';
17
16
  import { denyReasonForWire, isPlanReviewModeAfter } from './hitlBridge.js';
18
17
  import { freshPlanReviewQuestionId, planReviewQuestionId, REOPEN_ID_TAIL, taskIdFromPlanReviewQuestionId } from './gateIdentity.js';
19
18
  import { isPlanReviewPark, mintPlanReviewCardFacts, notePlanReviewCardIssued, pausedCheckpointIdOf, planReviewCardFactsFrom, planReviewFactsCurrent, registeredPlanReviewCardFacts, samePlanReviewCardFacts, unknownBody, _resetPlanReviewRunsForTest, } from '../planReviewCardFacts.js';
20
- import { planReviewAskAgainKey, planReviewCardNoteRef, planReviewUnreadableNoteRef, takePlanReviewFacts, _resetPlanReviewFactsFlightsForTest, } from './planReviewCard.js';
19
+ import { planReviewAskAgainKey, planReviewCardNoteRef, planReviewDecisionReasonRef, planReviewUnreadableNoteRef, takePlanReviewFacts, _resetPlanReviewFactsFlightsForTest, } from './planReviewCard.js';
21
20
  import { EN_MESSAGES } from '../i18n/messages.en.js';
22
21
  import { msg, renderMessage, DATA_FACE_LANGUAGE } from '../i18n/translate.js';
23
22
  import { notePlanReviewAnswered, notePlanReviewAnsweredFor, planReviewArmedKeyFor, waitForGateArmedFor, wasGateArmedFor, } from './armedGateRegistry.js';
@@ -183,6 +182,7 @@ const armedPlanReviews = new Map();
183
182
  const offeredModeByQuestionId = new Map();
184
183
  const OFFERED_MODE_LEDGER_MAX = 64;
185
184
  export function _resetArmedPlanReviewsForTest() {
185
+ engineRefusedGateIds.clear();
186
186
  armedPlanReviews.clear();
187
187
  offeredModeByQuestionId.clear();
188
188
  planReviewDismissalsByKey.clear();
@@ -519,7 +519,7 @@ export function armPlanReviewApproval(result, sessionKey, opts) {
519
519
  const cardDelivery = cardReason !== undefined ? { ...delivery, reason: cardReason } : delivery;
520
520
  notePlanReviewAnswered(questionId);
521
521
  hostLog('debug', `planReviewWire: arm responder delivering ${decided} for task ${taskId} (path=arm)`);
522
- void decidePlanReviewFor(taskId, decided, modeAfter, cardDelivery, gateBefore, facts);
522
+ void decidePlanReviewFor(taskId, decided, modeAfter, cardDelivery, facts);
523
523
  return { ok: true };
524
524
  };
525
525
  unregister = registerLocalQuestionResponder(questionId, responder);
@@ -534,7 +534,7 @@ export function planReviewOutcomeTail(taskId, decision, effect, cause) {
534
534
  switch (effect) {
535
535
  case 'took_effect':
536
536
  return decision === 'approve'
537
- ? `Use TaskOutput("${taskId}") if you need the resumed task's full output, then give the user a brief report of what was done.`
537
+ ? `The result of the resumed task ${JSON.stringify(taskId)} arrives in its completion notification; when it arrives, give the user a brief report of what was done.`
538
538
  : 'Briefly acknowledge to the user that the plan was rejected and continue planning.';
539
539
  case 'advanced':
540
540
  return `Tell the user the ${decision} went through and that another approval is now waiting for them; do not report the task as finished.`;
@@ -543,9 +543,9 @@ export function planReviewOutcomeTail(taskId, decision, effect, cause) {
543
543
  case 'not_sent':
544
544
  return `Nothing was sent to the engine. Do not tell the user the plan was ${decision === 'approve' ? 'approved' : 'rejected'}; the plan is still waiting for a decision.`;
545
545
  case 'not_applied':
546
- return `The ${decision} did not take effect. Do not tell the user the plan was ${decision === 'approve' ? 'approved' : 'rejected'}; check the task's current state with TaskOutput("${taskId}") before saying anything about the plan.`;
546
+ return `The ${decision} did not take effect. Do not tell the user the plan was ${decision === 'approve' ? 'approved' : 'rejected'}; find out where task ${JSON.stringify(taskId)} stands (the background task list or its next notification shows it) before saying anything about the plan.`;
547
547
  case 'unconfirmed':
548
- return `Do not state the result as fact: tell the user the ${decision} was submitted but its effect could not be confirmed; TaskOutput("${taskId}") shows where the task stands.`;
548
+ return `Do not state the result as fact: tell the user the ${decision} was submitted but its effect could not be confirmed; the background task list or the next notification of task ${JSON.stringify(taskId)} shows where it stands.`;
549
549
  }
550
550
  }
551
551
  function planReviewDecisionTimedOutOutcome(decision) {
@@ -632,12 +632,39 @@ export function latestPlanReviewDecisionMark(taskId) {
632
632
  export function __resetPlanReviewDecisionLatchForTests() {
633
633
  __resetDecisionLedgerForTests('plan_review');
634
634
  }
635
+ const engineRefusedGateIds = new Set();
636
+ const ENGINE_REFUSED_GATE_IDS_MAX = 64;
637
+ function noteEngineRefusedGate(id) {
638
+ if (engineRefusedGateIds.has(id))
639
+ return;
640
+ engineRefusedGateIds.add(id);
641
+ while (engineRefusedGateIds.size > ENGINE_REFUSED_GATE_IDS_MAX) {
642
+ const oldest = engineRefusedGateIds.values().next();
643
+ if (oldest.done === true)
644
+ break;
645
+ engineRefusedGateIds.delete(oldest.value);
646
+ }
647
+ }
648
+ function planReviewDecisionReasonSegment(reason, card) {
649
+ if (reason === undefined)
650
+ return '';
651
+ try {
652
+ return ` ${renderMessage(planReviewDecisionReasonRef(reason, card), DATA_FACE_LANGUAGE)}`;
653
+ }
654
+ catch {
655
+ return '';
656
+ }
657
+ }
635
658
  export async function decidePlanReview(taskId, decision, permissionModeAfter, opts) {
636
- return decidePlanReviewFor(taskId, decision, permissionModeAfter, opts, parkedGateIdentityOf(opts));
659
+ return decidePlanReviewFor(taskId, decision, permissionModeAfter, opts, decisionCardOf(taskId, opts));
637
660
  }
638
- function parkedGateIdentityOf(opts) {
661
+ function decisionCardOf(taskId, opts) {
639
662
  try {
640
- return opts !== null && typeof opts === 'object' ? pausedCheckpointIdOf(opts.parkedResult) : undefined;
663
+ if (opts === null || typeof opts !== 'object')
664
+ return undefined;
665
+ const raw = readOwnDataValue(opts, 'card');
666
+ const facts = raw !== null && typeof raw === 'object' ? planReviewCardFactsFrom(raw) : undefined;
667
+ return facts !== undefined && facts.taskId === taskId ? facts : undefined;
641
668
  }
642
669
  catch {
643
670
  return undefined;
@@ -691,7 +718,7 @@ function askAgainReasonOf(card, opts) {
691
718
  return renderMessage(msg('approval.planReview.askAgain.unknown'), DATA_FACE_LANGUAGE);
692
719
  }
693
720
  }
694
- async function decidePlanReviewFor(taskId, decision, permissionModeAfter, opts, gateBefore, card) {
721
+ async function decidePlanReviewFor(taskId, decision, permissionModeAfter, opts, card) {
695
722
  if (typeof taskId === 'string' && planReviewOpsOf(taskId).some((op) => op.phase === 'posting')) {
696
723
  hostLog('error', `planReviewWire: decidePlanReview(${decision}) for task ${taskId} REFUSED — a decision for this task is already in flight (duplicate delivery latched)`);
697
724
  return;
@@ -724,7 +751,7 @@ async function decidePlanReviewFor(taskId, decision, permissionModeAfter, opts,
724
751
  try {
725
752
  const wireDecision = decision === 'ask_again' ? 'reject' : decision;
726
753
  const wireOpts = decision === 'ask_again' ? { ...opts, reason: askAgainReasonOf(card, opts) } : opts;
727
- await decidePlanReviewInner(taskId, wireDecision, permissionModeAfter, dispatchNo, releaseLatch, settle, wireOpts, gateBefore, decision);
754
+ await decidePlanReviewInner(taskId, wireDecision, permissionModeAfter, dispatchNo, releaseLatch, settle, wireOpts, card, decision);
728
755
  }
729
756
  finally {
730
757
  releaseLatch();
@@ -745,8 +772,11 @@ function planReviewClientsFor(resolved) {
745
772
  repull: () => resolved.make({ timeoutMs: 15_000 }),
746
773
  };
747
774
  }
748
- async function decidePlanReviewInner(taskId, decision, permissionModeAfter, dispatchNo, releaseLatch, settle, opts, gateBefore, semanticDecision) {
775
+ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, dispatchNo, releaseLatch, settle, opts, card, semanticDecision) {
749
776
  let outcome;
777
+ let decisionReason;
778
+ const gateBefore = card?.checkpointId;
779
+ const approvalWithheld = decision !== 'approve' ? undefined : card === undefined ? 'card_missing' : card.body.kind !== 'shown' || card.checkpointId === undefined ? 'card_withholds' : undefined;
750
780
  let effect = 'unconfirmed';
751
781
  let stillParkedCause;
752
782
  let statusSeen;
@@ -758,6 +788,12 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
758
788
  effect = 'not_sent';
759
789
  outcome = `The plan_review ${decision} could NOT be sent: the post-approval permission mode given with it is not valid (it must be "default" or "acceptEdits"). Tell the user plainly that the decision did not go through; the plan is still waiting.`;
760
790
  }
791
+ else if (approvalWithheld !== undefined) {
792
+ hostLog('debug', `planReviewWire: not-sent decision=${decision} reason=${approvalWithheld}`);
793
+ effect = 'not_sent';
794
+ decisionReason = approvalWithheld;
795
+ outcome = renderMessage(msg('approval.planReview.decision.notSent', { decision }), DATA_FACE_LANGUAGE);
796
+ }
761
797
  else if (resolved.kind === 'no-target') {
762
798
  hostLog('error', `planReviewWire: ${decision} NOT sent — engineWireTarget() unavailable`);
763
799
  effect = 'not_sent';
@@ -777,11 +813,24 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
777
813
  }
778
814
  else {
779
815
  const client = clients.decide;
816
+ const checkpointId = gateBefore !== undefined && !engineRefusedGateIds.has(gateBefore) ? gateBefore : undefined;
817
+ const repullRow = async () => {
818
+ const probe = clients.repull();
819
+ if (!probe)
820
+ return undefined;
821
+ if (wire !== undefined) {
822
+ const got = await settleWithinDeadline(PLAN_REVIEW_INJECTED_REPULL_TIMEOUT_MS, (signal) => probe.runs.get(taskId, { signal }));
823
+ if (got.kind === 'error')
824
+ throw new Error(got.detail);
825
+ if (got.kind === 'deadline')
826
+ throw new Error(`post-decide status re-pull passed its ${PLAN_REVIEW_INJECTED_REPULL_TIMEOUT_MS} ms deadline`);
827
+ return got.value;
828
+ }
829
+ return (await probe.runs.get(taskId));
830
+ };
780
831
  try {
781
832
  const mode = isPlanReviewModeAfter(permissionModeAfter) ? permissionModeAfter : undefined;
782
833
  const reason = denyReasonForWire(opts?.reason, `plan-review ${taskId}`);
783
- const capsBase = resolved.kind === 'target' ? resolved.target.baseUrl : resolved.capsBaseUrl;
784
- const checkpointId = gateBefore !== undefined && engineCapTrue(capsBase, 'planReviewCheckpointId') ? gateBefore : undefined;
785
834
  const reqBody = { decision };
786
835
  if (reason !== undefined)
787
836
  reqBody.reason = reason;
@@ -812,20 +861,8 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
812
861
  let gateAfter;
813
862
  let gateKindAfter;
814
863
  try {
815
- const probe = clients.repull();
816
- if (probe) {
817
- let post;
818
- if (wire !== undefined) {
819
- const got = await settleWithinDeadline(PLAN_REVIEW_INJECTED_REPULL_TIMEOUT_MS, (signal) => probe.runs.get(taskId, { signal }));
820
- if (got.kind === 'error')
821
- throw new Error(got.detail);
822
- if (got.kind === 'deadline')
823
- throw new Error(`post-decide status re-pull passed its ${PLAN_REVIEW_INJECTED_REPULL_TIMEOUT_MS} ms deadline`);
824
- post = got.value;
825
- }
826
- else {
827
- post = (await probe.runs.get(taskId));
828
- }
864
+ const post = await repullRow();
865
+ if (post !== undefined) {
829
866
  postStatus = bodyWordOf(post, 'status');
830
867
  gateAfter = pausedCheckpointIdOf(post?.result);
831
868
  gateKindAfter = pausedGateKindOf(post?.result);
@@ -898,6 +935,36 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
898
935
  `This ${decision} was not applied: the engine no longer has a pending plan review for this task — an earlier decision most likely already went through (or the review expired). ` +
899
936
  'This is NOT a rejection of the plan and NOT a failure of the task; the answer just given simply had nothing left to act on.';
900
937
  }
938
+ else if (status === 409 && refusedCode === APPROVAL_STALE) {
939
+ let untrusted = false;
940
+ if (checkpointId !== undefined) {
941
+ const bag = readThrownExtra(e);
942
+ const noCurrentPending = bag.state === 'object' &&
943
+ (() => {
944
+ try {
945
+ return Object.getOwnPropertyDescriptor(bag.value, 'currentPending') === undefined;
946
+ }
947
+ catch {
948
+ return false;
949
+ }
950
+ })();
951
+ if (noCurrentPending) {
952
+ try {
953
+ const row = await repullRow();
954
+ untrusted = row !== undefined && pausedCheckpointIdOf(row?.result) === checkpointId;
955
+ }
956
+ catch {
957
+ untrusted = false;
958
+ }
959
+ }
960
+ }
961
+ if (untrusted && checkpointId !== undefined)
962
+ noteEngineRefusedGate(checkpointId);
963
+ hostLog('debug', `planReviewWire: approval-stale decision=${decision} reason=${untrusted ? 'gate_untrusted' : 'gate_moved'}`);
964
+ effect = 'not_applied';
965
+ decisionReason = untrusted ? 'gate_untrusted' : 'gate_moved';
966
+ outcome = renderMessage(msg('approval.planReview.decision.refused', { decision }), DATA_FACE_LANGUAGE);
967
+ }
901
968
  else if (checkpointUnsupportedVersionFromError(e) !== null) {
902
969
  const cu = checkpointUnsupportedVersionFromError(e);
903
970
  hostLog('debug', `planReviewWire: ${decision} → 409 checkpoint.unsupported_version (the server cannot continue this parked review in its stored form; still pending)`);
@@ -939,6 +1006,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
939
1006
  }
940
1007
  }
941
1008
  }
1009
+ outcome = `${outcome}${planReviewDecisionReasonSegment(decisionReason, card)}`;
942
1010
  const tail = planReviewOutcomeTail(taskId, decision, effect, stillParkedCause);
943
1011
  const prompt = `<plan-review-outcome>\n${outcome}\n${tail}\n</plan-review-outcome>`;
944
1012
  const onOutcome = opts?.onOutcome;
@@ -1087,10 +1155,10 @@ function mintPlanReviewReopenCard(taskId, opts, sessionKey, receiptMs, taken) {
1087
1155
  ? { ...(opts.wire !== undefined ? { wire: opts.wire } : {}), ...(opts.onOutcome !== undefined ? { onOutcome: opts.onOutcome } : {}) }
1088
1156
  : undefined;
1089
1157
  const deliver = opts?.deliverDecision ??
1090
- ((tid, decision, reason) => {
1158
+ ((tid, decision, reason, card) => {
1091
1159
  hostLog('debug', `planReviewWire: reopen responder delivering ${decision} for task ${tid} (path=reopen-default)`);
1092
1160
  const withReason = reason === undefined ? delivery : { ...delivery, reason };
1093
- void decidePlanReviewFor(tid, decision, undefined, withReason, parkedGateIdentityOf(withReason), facts);
1161
+ void decidePlanReviewFor(tid, decision, undefined, withReason, card);
1094
1162
  });
1095
1163
  const activeKey = `${sessionKey}\u0000${canonicalId}`;
1096
1164
  let unregister = () => { };
@@ -1116,10 +1184,7 @@ function mintPlanReviewReopenCard(taskId, opts, sessionKey, receiptMs, taken) {
1116
1184
  notePlanReviewDecisiveAnswerFor(sessionKey, taskId);
1117
1185
  notePlanReviewAnsweredFor(sessionKey, questionId);
1118
1186
  try {
1119
- if (cardReason === undefined)
1120
- await deliver(taskId, decided);
1121
- else
1122
- await deliver(taskId, decided, cardReason);
1187
+ await deliver(taskId, decided, cardReason, facts);
1123
1188
  }
1124
1189
  catch (e) {
1125
1190
  hostLog('error', `planReviewWire: reopen decision delivery threw for task ${taskId}: ${String(e)}`);
@@ -0,0 +1,2 @@
1
+ export type SessionAllowLeg = 'frame' | 'v1' | 'generic';
2
+ export declare function sessionAllowCarried(leg: SessionAllowLeg, approvalId: unknown): boolean;
@@ -0,0 +1,7 @@
1
+ export function sessionAllowCarried(leg, approvalId) {
2
+ if (leg === 'frame')
3
+ return true;
4
+ if (leg === 'v1')
5
+ return typeof approvalId === 'string' && approvalId !== '';
6
+ return false;
7
+ }
@@ -128,17 +128,11 @@ export interface SessionPolicyInertEntryRefusal {
128
128
  export declare function tightenSessionPolicy(facade: SessionPolicyFacade, req: SessionPolicyTightenRequest, opts?: {
129
129
  signal?: AbortSignal;
130
130
  }): Promise<SessionPolicyTightenOutcome>;
131
- export interface SessionPolicyTightenFacts {
132
- readonly agentsStartedBeforeFirstRule?: number | undefined;
133
- readonly workflowsRunning?: number | undefined;
134
- readonly untrackedBackgroundItems?: number | undefined;
135
- readonly inFlightUnreadable?: true | undefined;
136
- }
137
131
  export interface SessionPolicyDeploymentFacts {
138
132
  readonly operator?: 'present' | 'absent';
139
133
  readonly identityAttach?: 'available' | 'unavailable';
140
134
  }
141
- export declare function sessionPolicyTightenNotice(outcome: SessionPolicyTightenOutcome, lang?: UiLanguage, facts?: SessionPolicyTightenFacts, deployment?: SessionPolicyDeploymentFacts): string;
135
+ export declare function sessionPolicyTightenNotice(outcome: SessionPolicyTightenOutcome, lang?: UiLanguage, deployment?: SessionPolicyDeploymentFacts): string;
142
136
  export declare function sessionPolicyReadNotice(outcome: Exclude<SessionPolicyReadOutcome, {
143
137
  kind: 'present';
144
138
  }> | {
@@ -414,29 +414,6 @@ export async function tightenSessionPolicy(facade, req, opts) {
414
414
  return { kind: 'conflict', rev: second.retryAtRev };
415
415
  }
416
416
  const earlierGrammarNote = (lang) => renderMessage(msg('rules.sessionPolicy.tighten.earlierGrammar'), lang);
417
- function factSlot(facts, key) {
418
- try {
419
- return ownRead(facts, key);
420
- }
421
- catch {
422
- return undefined;
423
- }
424
- }
425
- function factCount(facts, key) {
426
- const n = factSlot(facts, key);
427
- return typeof n === 'number' && Number.isSafeInteger(n) && n >= 1 ? n : undefined;
428
- }
429
- function firstRuleWorkFactsOf(facts) {
430
- const none = { agents: undefined, workflows: undefined, untracked: undefined, unreadable: false };
431
- if (typeof facts !== 'object' || facts === null)
432
- return none;
433
- return {
434
- agents: factCount(facts, 'agentsStartedBeforeFirstRule'),
435
- workflows: factCount(facts, 'workflowsRunning'),
436
- untracked: factCount(facts, 'untrackedBackgroundItems'),
437
- unreadable: factSlot(facts, 'inFlightUnreadable') === true,
438
- };
439
- }
440
417
  function deploymentHasNoOperator(d) {
441
418
  try {
442
419
  return typeof d === 'object' && d !== null && ownRead(d, 'operator') === 'absent';
@@ -453,28 +430,11 @@ function deploymentHasNoIdentity(d) {
453
430
  return false;
454
431
  }
455
432
  }
456
- export function sessionPolicyTightenNotice(outcome, lang, facts, deployment) {
433
+ export function sessionPolicyTightenNotice(outcome, lang, deployment) {
457
434
  const say = (key, params) => renderMessage(msg(key, params), lang);
458
435
  switch (outcome.kind) {
459
- case 'written': {
460
- const head = say('rules.sessionPolicy.tighten.written', { rev: outcome.rev });
461
- const w = firstRuleWorkFactsOf(facts);
462
- const unseen = msg('rules.sessionPolicy.tighten.writtenBeforeFirstRule.unseen');
463
- if (w.unreadable)
464
- return `${head} ${renderMessage(msg('rules.sessionPolicy.tighten.writtenBeforeFirstRule.unreadable'), lang)} ${renderMessage(unseen, lang)}`;
465
- const parts = [];
466
- if (w.agents !== undefined)
467
- appendOwnElement(parts, msg(w.agents === 1 ? 'rules.sessionPolicy.tighten.writtenBeforeFirstRule.agents.one' : 'rules.sessionPolicy.tighten.writtenBeforeFirstRule.agents.many', { n: w.agents }));
468
- if (w.workflows !== undefined)
469
- appendOwnElement(parts, msg(w.workflows === 1 ? 'rules.sessionPolicy.tighten.writtenBeforeFirstRule.workflows.one' : 'rules.sessionPolicy.tighten.writtenBeforeFirstRule.workflows.many', { n: w.workflows }));
470
- if (w.untracked !== undefined)
471
- appendOwnElement(parts, msg(w.untracked === 1 ? 'rules.sessionPolicy.tighten.writtenBeforeFirstRule.untracked.one' : 'rules.sessionPolicy.tighten.writtenBeforeFirstRule.untracked.many', { n: w.untracked }));
472
- const [p0, p1, p2] = parts;
473
- if (p0 === undefined)
474
- return head;
475
- const list = p1 === undefined ? p0 : p2 === undefined ? msg('rules.sessionPolicy.tighten.writtenBeforeFirstRule.list.two', { a: p0, b: p1 }) : msg('rules.sessionPolicy.tighten.writtenBeforeFirstRule.list.three', { a: p0, b: p1, c: p2 });
476
- return `${head} ${renderMessage(msg('rules.sessionPolicy.tighten.writtenBeforeFirstRule.counts', { list }), lang)} ${renderMessage(unseen, lang)}`;
477
- }
436
+ case 'written':
437
+ return say('rules.sessionPolicy.tighten.written', { rev: outcome.rev });
478
438
  case 'conflict':
479
439
  return say('rules.sessionPolicy.tighten.conflict');
480
440
  case 'loosen_forbidden':
@@ -0,0 +1 @@
1
+ export declare function subagentSourceSessionOf(carrier: object | null | undefined): string | undefined;
@@ -0,0 +1,13 @@
1
+ import { slotReadOf } from '../wireFailureShape.js';
2
+ const nonEmpty = (v) => (typeof v === 'string' && v !== '' ? v : undefined);
3
+ export function subagentSourceSessionOf(carrier) {
4
+ if (typeof carrier !== 'object' || carrier === null)
5
+ return undefined;
6
+ const current = slotReadOf(carrier, 'sourceSessionId');
7
+ if (current.presence === 'present')
8
+ return nonEmpty(current.value);
9
+ if (current.presence !== 'absent')
10
+ return undefined;
11
+ const retired = slotReadOf(carrier, 'sourceTaskId');
12
+ return retired.presence === 'present' ? nonEmpty(retired.value) : undefined;
13
+ }
@@ -163,7 +163,7 @@ export declare function approvalCardPortFor(sessionKey: string): ApprovalCardPor
163
163
  export declare function __resetSettledApprovalsForTests(): void;
164
164
  export declare function _resetApprovalCardPortForTest(): void;
165
165
  export declare function surfaceApprovalCard(req: ApprovalCardRequest): Promise<ApprovalCardDecision>;
166
- declare const REMEMBER_REFUSAL_CODES: readonly ["remember_not_in_proof", "remember_requires_binding", "capability.store_required", "decide.workflow_remember_unsupported", "decide.parked_remember_unsupported"];
166
+ declare const REMEMBER_REFUSAL_CODES: readonly ["remember_not_in_proof", "capability.store_required", "decide.workflow_remember_unsupported", "decide.parked_remember_unsupported"];
167
167
  export type RememberRefusalCode = (typeof REMEMBER_REFUSAL_CODES)[number];
168
168
  export declare function rememberRefusalOf(error: unknown): RememberRefusalCode | undefined;
169
169
  export declare function surfaceFsApprovalAndDecide(deps: FsApprovalWireDeps, taskId: string, argsByCall: Map<string, unknown>, signal?: AbortSignal, parkGatedCallId?: string, parkToolName?: string): Promise<ApprovalResolution>;
@@ -179,7 +179,7 @@ export interface ReadRootCandidateView {
179
179
  covers?: 'exact';
180
180
  }
181
181
  export type ToolApprovalDelegation = ApprovalCardDelegation;
182
- export declare const TOOL_APPROVAL_FRAME_KEYS_MIRROR: readonly ["type", "approvalId", "toolCallId", "toolName", "sourceTaskId", "fromSubagent", "sourceAgentName", "message", "args", "argsOmitted", "governanceForced", "ruleOffers", "ruleOffersAbsence", "denialLimitFallback", "readRootCandidate", "mandated", "mandate", "origin", "approvalCard", "askClass", "classes", "inputHasBidi", "parked", "persistedRuleShadowed", "allowRuleShadowed", "probeCause", "probeReason", "rememberRefused", "boundGrantDigest", "boundGrantAbsence", "boundGrants", "classifierNoRuling", "ruleEvidence", "requiresRealApproval", "expiresInMs", "expiresAtMs", "serverNowMs", "delegation", "outcome", "cause", "requestedBy"];
182
+ export declare const TOOL_APPROVAL_FRAME_KEYS_MIRROR: readonly ["type", "approvalId", "toolCallId", "toolName", "sourceTaskId", "sourceSessionId", "agentId", "askReasons", "fromSubagent", "sourceAgentName", "message", "args", "argsOmitted", "governanceForced", "ruleOffers", "ruleOffersAbsence", "denialLimitFallback", "readRootCandidate", "mandated", "mandate", "origin", "approvalCard", "askClass", "classes", "inputHasBidi", "parked", "persistedRuleShadowed", "allowRuleShadowed", "probeCause", "probeReason", "rememberRefused", "boundGrantDigest", "boundGrantAbsence", "boundGrants", "classifierNoRuling", "ruleEvidence", "requiresRealApproval", "expiresInMs", "expiresAtMs", "serverNowMs", "delegation", "outcome", "cause", "requestedBy"];
183
183
  export interface ApprovalMandatedFacts {
184
184
  readonly mandated?: true;
185
185
  readonly ruleOffersAbsence?: string;
@@ -283,6 +283,7 @@ export interface ApprovalCardFrameFactsOptions {
283
283
  readonly argsUnavailable?: true;
284
284
  }
285
285
  export declare function approvalCardFactsOfV1(card: object | null | undefined): Readonly<ApprovalCardV1Facts> | undefined;
286
+ export declare function approvalCardFactsOfV1Frame(frame: object | null | undefined): Readonly<ApprovalCardFacts> | undefined;
286
287
  export declare function approvalCardFactsOfFrame(frame: object | null | undefined, opts?: ApprovalCardFrameFactsOptions | null): Readonly<ApprovalCardFacts> | undefined;
287
288
  export declare function approvalCardFactsOfGeneric(card: object | null | undefined): Readonly<ApprovalCardFacts> | undefined;
288
289
  export interface ToolApprovalFrameLaneOpts {