@sema-agent/client-core 0.111.0 → 0.112.1

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 (115) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/README.md +16 -7
  3. package/dist/agentSession/engineSessionBackgroundStop.js +2 -2
  4. package/dist/checkpointRowVerdict.d.ts +3 -0
  5. package/dist/checkpointRowVerdict.js +14 -1
  6. package/dist/consoleCaPin.d.ts +136 -0
  7. package/dist/consoleCaPin.js +827 -0
  8. package/dist/decideReceipt.d.ts +2 -1
  9. package/dist/deviceRebind.js +3 -3
  10. package/dist/effectiveFacts.d.ts +9 -0
  11. package/dist/effectiveFacts.js +33 -0
  12. package/dist/engineAddress.d.ts +1 -0
  13. package/dist/engineAddress.js +4 -1
  14. package/dist/engineCredentialRelease.d.ts +54 -0
  15. package/dist/engineCredentialRelease.js +146 -0
  16. package/dist/engineErrorCodes.d.ts +1 -0
  17. package/dist/engineErrorCodes.js +1 -0
  18. package/dist/engineHealthFacts.d.ts +0 -11
  19. package/dist/engineHealthFacts.js +0 -21
  20. package/dist/engineHealthProbeVerdict.d.ts +1 -0
  21. package/dist/engineHealthProbeVerdict.js +21 -13
  22. package/dist/engineUnavailable.d.ts +3 -2
  23. package/dist/engineUnavailable.js +16 -12
  24. package/dist/engineUrl.js +4 -2
  25. package/dist/gateFailureCodes.js +2 -2
  26. package/dist/generated/sdkFactTables.d.ts +1 -0
  27. package/dist/generated/sdkFactTables.js +13 -0
  28. package/dist/generated/settingsSchemaFns.d.ts +1 -0
  29. package/dist/generated/settingsSchemaFns.js +3 -0
  30. package/dist/generated/settingsSchemaHooksClassifier.js +38 -9
  31. package/dist/generated/settingsSchemaTables.d.ts +52 -0
  32. package/dist/generated/settingsSchemaTables.js +18 -0
  33. package/dist/generated/settingsSchemaTypes.d.ts +23 -13
  34. package/dist/generated/settingsSchemaUrlShape.d.ts +1 -0
  35. package/dist/generated/settingsSchemaUrlShape.js +17 -0
  36. package/dist/hitl/decisionRefusal.d.ts +4 -0
  37. package/dist/hitl/decisionRefusal.js +12 -2
  38. package/dist/hitl/hitlBridge.d.ts +1 -0
  39. package/dist/hitl/hitlBridge.js +17 -4
  40. package/dist/hitl/localAllowRedemption.d.ts +9 -6
  41. package/dist/hitl/localAllowRedemption.js +20 -0
  42. package/dist/hitl/parkOwnership.js +2 -1
  43. package/dist/hitl/parkResolver.js +7 -0
  44. package/dist/hitl/planReviewCard.d.ts +3 -2
  45. package/dist/hitl/planReviewCard.js +2 -0
  46. package/dist/hitl/planReviewWire.d.ts +4 -2
  47. package/dist/hitl/planReviewWire.js +134 -97
  48. package/dist/hitl/screenDecisionCards.js +3 -2
  49. package/dist/hitl/sessionPolicyWire.js +2 -2
  50. package/dist/hitl/standingRows.d.ts +4 -2
  51. package/dist/hitl/standingRows.js +10 -3
  52. package/dist/hooksReaderGenerations.js +1 -1
  53. package/dist/i18n/messages.en.d.ts +165 -0
  54. package/dist/i18n/messages.en.js +166 -1
  55. package/dist/i18n/messages.zh.js +177 -1
  56. package/dist/index.d.ts +30 -5
  57. package/dist/index.js +15 -2
  58. package/dist/liveModelCatalog.d.ts +4 -0
  59. package/dist/liveModelCatalog.js +10 -1
  60. package/dist/localConfig/documents.d.ts +61 -0
  61. package/dist/localConfig/documents.js +51 -0
  62. package/dist/localConfig/kernel.d.ts +149 -0
  63. package/dist/localConfig/kernel.js +597 -0
  64. package/dist/localConfig/mcp/edit.d.ts +161 -0
  65. package/dist/localConfig/mcp/edit.js +846 -0
  66. package/dist/localConfig/mcp/servers.d.ts +165 -0
  67. package/dist/localConfig/mcp/servers.js +776 -0
  68. package/dist/localConfig/mcp/views.d.ts +170 -0
  69. package/dist/localConfig/mcp/views.js +429 -0
  70. package/dist/localConfig/modelPool.d.ts +262 -0
  71. package/dist/localConfig/modelPool.js +1489 -0
  72. package/dist/localConfig/modelPoolWords.d.ts +13 -0
  73. package/dist/localConfig/modelPoolWords.js +20 -0
  74. package/dist/localConfig/receiptShape.d.ts +7 -0
  75. package/dist/localConfig/receiptShape.js +135 -0
  76. package/dist/localConfig/settingsWrite.d.ts +127 -0
  77. package/dist/localConfig/settingsWrite.js +237 -0
  78. package/dist/localConfig/skills.d.ts +124 -0
  79. package/dist/localConfig/skills.js +397 -0
  80. package/dist/localConfig/skillsWords.d.ts +10 -0
  81. package/dist/localConfig/skillsWords.js +5 -0
  82. package/dist/mcpProbeWire.js +2 -1
  83. package/dist/mcpWireCaps.js +1 -1
  84. package/dist/memoryVerbsWire.js +3 -2
  85. package/dist/model/onboardJudgement.d.ts +14 -4
  86. package/dist/model/onboardJudgement.js +91 -139
  87. package/dist/modelConfigGapNotice.d.ts +20 -0
  88. package/dist/modelConfigGapNotice.js +102 -0
  89. package/dist/modsKernelIdentity.js +2 -47
  90. package/dist/notifications.js +14 -12
  91. package/dist/ownKey.d.ts +1 -0
  92. package/dist/ownKey.js +40 -8
  93. package/dist/permissionsDeclaration.d.ts +2 -1
  94. package/dist/permissionsDeclaration.js +31 -17
  95. package/dist/planReviewOutcomeCause.d.ts +2 -0
  96. package/dist/planReviewOutcomeCause.js +12 -0
  97. package/dist/promptBlockedCopy.js +2 -2
  98. package/dist/resumeRefusalCopy.d.ts +3 -1
  99. package/dist/resumeRefusalCopy.js +12 -4
  100. package/dist/runLedgerSnapshot.js +3 -3
  101. package/dist/runPermissionModeSwitch.js +2 -3
  102. package/dist/seatContract.d.ts +154 -4
  103. package/dist/seatContract.js +228 -2
  104. package/dist/sessionIdSegment.d.ts +1 -0
  105. package/dist/sessionIdSegment.js +4 -1
  106. package/dist/sessionMemoryErase.js +2 -1
  107. package/dist/sha256Bytes.d.ts +1 -0
  108. package/dist/sha256Bytes.js +46 -0
  109. package/dist/skillOverrideChain.d.ts +83 -0
  110. package/dist/skillOverrideChain.js +378 -0
  111. package/dist/skillsWireCaps.d.ts +3 -0
  112. package/dist/skillsWireCaps.js +16 -1
  113. package/dist/turnFailurePresentation.js +4 -10
  114. package/docs/INTEGRATION-CLIENTS.md +680 -14
  115. package/package.json +3 -3
@@ -18,6 +18,7 @@ import { denyReasonForWire, isPlanReviewModeAfter } from './hitlBridge.js';
18
18
  import { freshPlanReviewQuestionId, planReviewQuestionId, REOPEN_ID_TAIL, taskIdFromPlanReviewQuestionId } from './gateIdentity.js';
19
19
  import { isPlanReviewPark, mintPlanReviewCardFacts, notePlanReviewCardIssued, pausedCheckpointIdOf, planReviewCardFactsFrom, planReviewFactsCurrent, registeredPlanReviewCardFacts, samePlanReviewCardFacts, unknownBody, _resetPlanReviewRunsForTest, } from '../planReviewCardFacts.js';
20
20
  import { planReviewAskAgainKey, planReviewCardNoteRef, planReviewDecisionReasonRef, planReviewUnreadableNoteRef, takePlanReviewFacts, _resetPlanReviewFactsFlightsForTest, } from './planReviewCard.js';
21
+ import { planReviewOutcomeCauseOf } from '../planReviewOutcomeCause.js';
21
22
  import { EN_MESSAGES } from '../i18n/messages.en.js';
22
23
  import { msg, renderMessage, DATA_FACE_LANGUAGE } from '../i18n/translate.js';
23
24
  import { notePlanReviewAnswered, notePlanReviewAnsweredFor, planReviewArmedKeyFor, waitForGateArmedFor, wasGateArmedFor, } from './armedGateRegistry.js';
@@ -666,11 +667,34 @@ function noteEngineRefusedGate(id) {
666
667
  engineRefusedGateIds.delete(oldest.value);
667
668
  }
668
669
  }
670
+ function wireGateOf(decision, gate) {
671
+ if (gate === undefined)
672
+ return undefined;
673
+ if (decision === 'approve')
674
+ return gate;
675
+ return engineRefusedGateIds.has(gate) ? undefined : gate;
676
+ }
677
+ function planReviewWireBodyOf(decision, reason, mode, gate) {
678
+ const withReason = reason !== undefined ? { reason } : {};
679
+ const withMode = mode !== undefined ? { permissionModeAfter: mode } : {};
680
+ if (decision === 'approve')
681
+ return gate === undefined ? undefined : Object.freeze({ decision, ...withReason, ...withMode, checkpointId: gate });
682
+ return Object.freeze({ decision, ...withReason, ...withMode, ...(gate !== undefined ? { checkpointId: gate } : {}) });
683
+ }
669
684
  function planReviewDecisionReasonSegment(reason, card) {
670
685
  if (reason === undefined)
671
686
  return '';
672
687
  try {
673
- return ` ${renderMessage(planReviewDecisionReasonRef(reason, card), DATA_FACE_LANGUAGE)}`;
688
+ const ref = planReviewDecisionReasonRef(reason, card);
689
+ return ref === undefined ? '' : ` ${renderMessage(ref, DATA_FACE_LANGUAGE)}`;
690
+ }
691
+ catch {
692
+ return '';
693
+ }
694
+ }
695
+ function planReviewSentWithoutGateSegment() {
696
+ try {
697
+ return ` ${renderMessage(msg('approval.planReview.decision.sentWithoutGateId'), DATA_FACE_LANGUAGE)}`;
674
698
  }
675
699
  catch {
676
700
  return '';
@@ -741,9 +765,17 @@ function askAgainReasonOf(card, opts) {
741
765
  return renderMessage(msg('approval.planReview.askAgain.unknown'), DATA_FACE_LANGUAGE);
742
766
  }
743
767
  }
768
+ function planReviewDecisionWordOf(raw) {
769
+ return raw === 'approve' || raw === 'reject' || raw === 'ask_again' ? raw : undefined;
770
+ }
744
771
  async function decidePlanReviewFor(taskId, decision, permissionModeAfter, opts, card, cardId) {
772
+ const word = planReviewDecisionWordOf(decision);
773
+ if (word === undefined) {
774
+ hostLog('error', `planReviewWire: decision-word-refused typeof=${typeof decision}`);
775
+ return;
776
+ }
745
777
  if (typeof taskId === 'string' && planReviewOpsOf(taskId).some((op) => op.phase === 'posting')) {
746
- hostLog('error', `planReviewWire: decidePlanReview(${decision}) for task ${taskId} REFUSED — a decision for this task is already in flight (duplicate delivery latched)`);
778
+ hostLog('error', `planReviewWire: decidePlanReview(${word}) for task ${taskId} REFUSED — a decision for this task is already in flight (duplicate delivery latched)`);
747
779
  return;
748
780
  }
749
781
  let op = typeof taskId === 'string' ? claimablePlanReviewDecisionOp(taskId) : undefined;
@@ -770,11 +802,11 @@ async function decidePlanReviewFor(taskId, decision, permissionModeAfter, opts,
770
802
  settleDecisionOp(mine, 'package');
771
803
  };
772
804
  const dispatchNo = ++decideDispatchSeq;
773
- hostLog('debug', `planReviewWire: decidePlanReview dispatch #${dispatchNo} task=${taskId} decision=${decision} mode=${permissionModeAfter ?? '-'}`);
805
+ hostLog('debug', `planReviewWire: decidePlanReview dispatch #${dispatchNo} task=${taskId} decision=${word} mode=${permissionModeAfter ?? '-'}`);
774
806
  try {
775
- const wireDecision = decision === 'ask_again' ? 'reject' : decision;
776
- const wireOpts = decision === 'ask_again' ? { ...opts, reason: askAgainReasonOf(card, opts) } : opts;
777
- await decidePlanReviewInner(taskId, wireDecision, permissionModeAfter, dispatchNo, releaseLatch, settle, wireOpts, card, decision, cardId);
807
+ const wireDecision = word === 'ask_again' ? 'reject' : word;
808
+ const wireOpts = word === 'ask_again' ? { ...opts, reason: askAgainReasonOf(card, opts) } : opts;
809
+ await decidePlanReviewInner(taskId, wireDecision, permissionModeAfter, dispatchNo, releaseLatch, settle, wireOpts, card, word, cardId);
778
810
  }
779
811
  finally {
780
812
  releaseLatch();
@@ -797,14 +829,15 @@ function planReviewClientsFor(resolved) {
797
829
  }
798
830
  async function decidePlanReviewInner(taskId, decision, permissionModeAfter, dispatchNo, releaseLatch, settle, opts, card, semanticDecision, cardId) {
799
831
  let outcome;
800
- let decisionReason;
832
+ let cause;
833
+ let unsentProven = false;
801
834
  const gateBefore = card?.checkpointId;
802
835
  const approvalWithheld = decision !== 'approve' ? undefined : card === undefined ? 'card_missing' : card.body.kind !== 'shown' || card.checkpointId === undefined ? 'card_withholds' : undefined;
803
836
  let effect = 'unconfirmed';
804
- let stillParkedCause;
805
837
  let statusSeen;
806
838
  const wire = opts?.wire;
807
839
  const resolved = engineWireFor(wire, DEFAULT_SESSION_KEY, usablePlanReviewWireClient);
840
+ const wireGate = wireGateOf(decision, gateBefore);
808
841
  const badMode = permissionModeAfter !== undefined && !isPlanReviewModeAfter(permissionModeAfter);
809
842
  if (badMode) {
810
843
  hostLog('error', `planReviewWire: ${decision} NOT sent — permissionModeAfter must be "default" or "acceptEdits"`);
@@ -814,7 +847,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
814
847
  else if (approvalWithheld !== undefined) {
815
848
  hostLog('debug', `planReviewWire: not-sent decision=${decision} reason=${approvalWithheld}`);
816
849
  effect = 'not_sent';
817
- decisionReason = approvalWithheld;
850
+ cause = approvalWithheld;
818
851
  outcome = renderMessage(msg('approval.planReview.decision.notSent', { decision }), DATA_FACE_LANGUAGE);
819
852
  }
820
853
  else if (resolved.kind === 'no-target') {
@@ -836,7 +869,6 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
836
869
  }
837
870
  else {
838
871
  const client = clients.decide;
839
- const checkpointId = gateBefore !== undefined && !engineRefusedGateIds.has(gateBefore) ? gateBefore : undefined;
840
872
  const repullRow = async () => {
841
873
  const probe = clients.repull();
842
874
  if (!probe)
@@ -854,87 +886,90 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
854
886
  try {
855
887
  const mode = isPlanReviewModeAfter(permissionModeAfter) ? permissionModeAfter : undefined;
856
888
  const reason = denyReasonForWire(opts?.reason, `plan-review ${taskId}`);
857
- const reqBody = { decision };
858
- if (reason !== undefined)
859
- reqBody.reason = reason;
860
- if (mode !== undefined)
861
- reqBody.permissionModeAfter = mode;
862
- if (checkpointId !== undefined)
863
- reqBody.checkpointId = checkpointId;
864
- let body;
865
- try {
866
- body = await sendOnce(undefined, () => client.assistant.planReview(taskId, reqBody));
889
+ const wireBody = planReviewWireBodyOf(decision, reason, mode, wireGate);
890
+ if (wireBody === undefined) {
891
+ effect = 'not_sent';
892
+ cause = 'card_withholds';
893
+ outcome = renderMessage(msg('approval.planReview.decision.notSent', { decision }), DATA_FACE_LANGUAGE);
867
894
  }
868
- catch (e1) {
869
- const conflict = e1;
870
- const defaultConflict = mode === 'default' && conflict?.status === 400 && conflict?.errorCode === REQUEST_FIELD_CONFLICT;
871
- if (defaultConflict && !directFailureVerdictOf(e1, directSafetyStopOf(e1)).yields) {
872
- hostLog('debug', 'planReviewWire: permissionModeAfter "default" refused as not applicable — re-sending the approval without the key (same meaning)');
873
- body = await sendOnce(undefined, () => client.assistant.planReview(taskId, { decision, ...(reason !== undefined ? { reason } : {}), ...(checkpointId !== undefined ? { checkpointId } : {}) }));
895
+ else {
896
+ const firstSent = { ...wireBody };
897
+ let body;
898
+ try {
899
+ body = await sendOnce(undefined, () => client.assistant.planReview(taskId, firstSent));
874
900
  }
875
- else {
876
- throw e1;
901
+ catch (e1) {
902
+ const conflict = e1;
903
+ const defaultConflict = mode === 'default' && conflict?.status === 400 && conflict?.errorCode === REQUEST_FIELD_CONFLICT;
904
+ if (defaultConflict && !directFailureVerdictOf(e1, directSafetyStopOf(e1)).yields) {
905
+ hostLog('debug', 'planReviewWire: permissionModeAfter "default" refused as not applicable — re-sending the approval without the key (same meaning)');
906
+ const { permissionModeAfter: _modeDropped, ...resend } = wireBody;
907
+ body = await sendOnce(undefined, () => client.assistant.planReview(taskId, resend));
908
+ }
909
+ else {
910
+ throw e1;
911
+ }
877
912
  }
878
- }
879
- finally {
880
- releaseLatch();
881
- }
882
- hostLog('debug', `planReviewWire: ${decision} → ok (dispatch #${dispatchNo}) ${JSON.stringify(body).slice(0, 200)}`);
883
- let postStatus;
884
- let gateAfter;
885
- let gateKindAfter;
886
- try {
887
- const post = await repullRow();
888
- if (post !== undefined) {
889
- postStatus = bodyWordOf(post, 'status');
890
- gateAfter = pausedCheckpointIdOf(post?.result);
891
- gateKindAfter = pausedGateKindOf(post?.result);
913
+ finally {
914
+ releaseLatch();
915
+ }
916
+ hostLog('debug', `planReviewWire: ${decision} → ok (dispatch #${dispatchNo}) ${JSON.stringify(body).slice(0, 200)}`);
917
+ let postStatus;
918
+ let gateAfter;
919
+ let gateKindAfter;
920
+ try {
921
+ const post = await repullRow();
922
+ if (post !== undefined) {
923
+ postStatus = bodyWordOf(post, 'status');
924
+ gateAfter = pausedCheckpointIdOf(post?.result);
925
+ gateKindAfter = pausedGateKindOf(post?.result);
926
+ }
927
+ }
928
+ catch (e) {
929
+ hostLog('debug', `planReviewWire: post-decide status re-pull failed (fail-soft): ${String(e)}`);
930
+ }
931
+ const bodyStatus = bodyWordOf(body, 'status');
932
+ const effective = postStatus ?? bodyStatus;
933
+ statusSeen = effective;
934
+ effect = planReviewSettledVerdict({ decision, postStatus, bodyStatus, reparked: bodySaysReparked(body), gateBefore, gateAfter, gateKindAfter });
935
+ const code = bodyWordOf(body, 'errorCode');
936
+ if (effect === 'still_parked' && postStatus === 'needs_review' && !bodySaysReparked(body)) {
937
+ outcome =
938
+ `The plan_review ${decision} returned HTTP 200 but the session is STILL locked on the same review gate ` +
939
+ `(post-decide status: needs_review). The decision did NOT take effect (known engine issue, RB-471 family). ` +
940
+ `Tell the user plainly that the ${decision} did not go through; the reliable exits today are approving the plan or cancelling the task.`;
941
+ }
942
+ else if (effect === 'still_parked') {
943
+ outcome =
944
+ `The plan_review ${decision} was NOT applied: the engine could not continue the task and put the plan back to wait for a decision` +
945
+ `${code !== undefined ? ` (engine code: ${code})` : ''}. Tell the user plainly that the ${decision} did not go through.`;
946
+ }
947
+ else if (effect === 'advanced') {
948
+ outcome = planReviewAdvancedOutcome(decision, effective === 'needs_review' ? 'needs_review' : 'suspended');
949
+ }
950
+ else if (effect === 'took_effect') {
951
+ outcome =
952
+ `The plan was ${decision === 'approve' ? 'approved and the parked task resumed to completion' : 'rejected (plan discarded)'} — ${effective === 'completed' ? 'final status' : 'status'}: ${effective} ` +
953
+ `(${postStatus !== undefined ? 'post-decide re-checked: task left the review gate' : "from the decision's own reply; the post-decide re-check was unavailable"}).`;
954
+ }
955
+ else if (effective === 'needs_review' && (gateBefore === undefined || gateAfter === undefined || postStatus !== 'needs_review')) {
956
+ outcome =
957
+ `The plan_review ${decision} returned HTTP 200 and the task is waiting at a plan review gate again (post-decide status: needs_review), ` +
958
+ `but it could not be told whether this is the same review (the ${decision} did not take effect) or a new plan written after it (it did).`;
959
+ }
960
+ else if (effective === 'suspended' || effective === 'needs_review') {
961
+ outcome =
962
+ `The plan_review ${decision} was accepted by the engine (HTTP 200) and the task is paused again (post-decide status: ${effective}), ` +
963
+ `${gateKindAfter !== undefined ? `at a '${gateKindAfter}' pause that is not an approval card` : 'but the pause it stopped at was not reported'} — what the ${decision} led to could not be confirmed.`;
964
+ }
965
+ else if (effective !== undefined) {
966
+ outcome =
967
+ `The plan_review ${decision} was accepted by the engine (HTTP 200), but the task then reported status ${effective}` +
968
+ `${code !== undefined ? ` (engine code: ${code})` : ''} — not a completion; what the ${decision} led to could not be confirmed.`;
969
+ }
970
+ else {
971
+ outcome = `The plan_review ${decision} was accepted by the engine (HTTP 200) but its effect is unconfirmed — final status: unknown (post-decide verification unavailable). Do not treat this as ${decision === 'approve' ? 'an approval that resumed the task' : 'a discarded plan'}.`;
892
972
  }
893
- }
894
- catch (e) {
895
- hostLog('debug', `planReviewWire: post-decide status re-pull failed (fail-soft): ${String(e)}`);
896
- }
897
- const bodyStatus = bodyWordOf(body, 'status');
898
- const effective = postStatus ?? bodyStatus;
899
- statusSeen = effective;
900
- effect = planReviewSettledVerdict({ decision, postStatus, bodyStatus, reparked: bodySaysReparked(body), gateBefore, gateAfter, gateKindAfter });
901
- const code = bodyWordOf(body, 'errorCode');
902
- if (effect === 'still_parked' && postStatus === 'needs_review' && !bodySaysReparked(body)) {
903
- outcome =
904
- `The plan_review ${decision} returned HTTP 200 but the session is STILL locked on the same review gate ` +
905
- `(post-decide status: needs_review). The decision did NOT take effect (known engine issue, RB-471 family). ` +
906
- `Tell the user plainly that the ${decision} did not go through; the reliable exits today are approving the plan or cancelling the task.`;
907
- }
908
- else if (effect === 'still_parked') {
909
- outcome =
910
- `The plan_review ${decision} was NOT applied: the engine could not continue the task and put the plan back to wait for a decision` +
911
- `${code !== undefined ? ` (engine code: ${code})` : ''}. Tell the user plainly that the ${decision} did not go through.`;
912
- }
913
- else if (effect === 'advanced') {
914
- outcome = planReviewAdvancedOutcome(decision, effective === 'needs_review' ? 'needs_review' : 'suspended');
915
- }
916
- else if (effect === 'took_effect') {
917
- outcome =
918
- `The plan was ${decision === 'approve' ? 'approved and the parked task resumed to completion' : 'rejected (plan discarded)'} — ${effective === 'completed' ? 'final status' : 'status'}: ${effective} ` +
919
- `(${postStatus !== undefined ? 'post-decide re-checked: task left the review gate' : "from the decision's own reply; the post-decide re-check was unavailable"}).`;
920
- }
921
- else if (effective === 'needs_review' && (gateBefore === undefined || gateAfter === undefined || postStatus !== 'needs_review')) {
922
- outcome =
923
- `The plan_review ${decision} returned HTTP 200 and the task is waiting at a plan review gate again (post-decide status: needs_review), ` +
924
- `but it could not be told whether this is the same review (the ${decision} did not take effect) or a new plan written after it (it did).`;
925
- }
926
- else if (effective === 'suspended' || effective === 'needs_review') {
927
- outcome =
928
- `The plan_review ${decision} was accepted by the engine (HTTP 200) and the task is paused again (post-decide status: ${effective}), ` +
929
- `${gateKindAfter !== undefined ? `at a '${gateKindAfter}' pause that is not an approval card` : 'but the pause it stopped at was not reported'} — what the ${decision} led to could not be confirmed.`;
930
- }
931
- else if (effective !== undefined) {
932
- outcome =
933
- `The plan_review ${decision} was accepted by the engine (HTTP 200), but the task then reported status ${effective}` +
934
- `${code !== undefined ? ` (engine code: ${code})` : ''} — not a completion; what the ${decision} led to could not be confirmed.`;
935
- }
936
- else {
937
- outcome = `The plan_review ${decision} was accepted by the engine (HTTP 200) but its effect is unconfirmed — final status: unknown (post-decide verification unavailable). Do not treat this as ${decision === 'approve' ? 'an approval that resumed the task' : 'a discarded plan'}.`;
938
973
  }
939
974
  }
940
975
  catch (e) {
@@ -960,7 +995,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
960
995
  }
961
996
  else if (status === 409 && refusedCode === APPROVAL_STALE) {
962
997
  let untrusted = false;
963
- if (checkpointId !== undefined) {
998
+ if (wireGate !== undefined) {
964
999
  const bag = readThrownExtra(e);
965
1000
  const noCurrentPending = bag.state === 'object' &&
966
1001
  (() => {
@@ -974,25 +1009,25 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
974
1009
  if (noCurrentPending) {
975
1010
  try {
976
1011
  const row = await repullRow();
977
- untrusted = row !== undefined && pausedCheckpointIdOf(row?.result) === checkpointId;
1012
+ untrusted = row !== undefined && pausedCheckpointIdOf(row?.result) === wireGate;
978
1013
  }
979
1014
  catch {
980
1015
  untrusted = false;
981
1016
  }
982
1017
  }
983
1018
  }
984
- if (untrusted && checkpointId !== undefined)
985
- noteEngineRefusedGate(checkpointId);
1019
+ if (untrusted && wireGate !== undefined)
1020
+ noteEngineRefusedGate(wireGate);
986
1021
  hostLog('debug', `planReviewWire: approval-stale decision=${decision} reason=${untrusted ? 'gate_untrusted' : 'gate_moved'}`);
987
1022
  effect = 'not_applied';
988
- decisionReason = untrusted ? 'gate_untrusted' : 'gate_moved';
1023
+ cause = untrusted ? 'gate_untrusted' : 'gate_moved';
989
1024
  outcome = renderMessage(msg('approval.planReview.decision.refused', { decision }), DATA_FACE_LANGUAGE);
990
1025
  }
991
1026
  else if (checkpointUnsupportedVersionFromError(e) !== null) {
992
1027
  const cu = checkpointUnsupportedVersionFromError(e);
993
1028
  hostLog('debug', `planReviewWire: ${decision} → 409 checkpoint.unsupported_version (the server cannot continue this parked review in its stored form; still pending)`);
994
1029
  effect = 'still_parked';
995
- stillParkedCause = 'stored_form_unsupported';
1030
+ cause = 'stored_form_unsupported';
996
1031
  outcome = `This ${decision} was not applied. ${checkpointUnsupportedVersionContent(cu, { parkKind: 'plan_review', runId: taskId })}`;
997
1032
  }
998
1033
  else if (resumeContextUnavailableFromError(e) !== null) {
@@ -1024,18 +1059,20 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
1024
1059
  else {
1025
1060
  const failureTexts = wireFailureTextsOf(e);
1026
1061
  hostLog('debug', `planReviewWire: ${decision} could not reach the engine: ${failureTexts.log}`);
1062
+ unsentProven = true;
1027
1063
  outcome = `The plan_review decision could not reach the engine: ${failureTexts.reason}`;
1028
1064
  }
1029
1065
  }
1030
1066
  }
1031
1067
  }
1032
- outcome = `${outcome}${planReviewDecisionReasonSegment(decisionReason, card)}`;
1033
- const tail = planReviewOutcomeTail(taskId, decision, effect, stillParkedCause);
1068
+ const outcomeCause = planReviewOutcomeCauseOf(effect, cause);
1069
+ outcome = `${outcome}${planReviewDecisionReasonSegment(outcomeCause, card)}${gateBefore !== undefined && wireGate === undefined && effect !== 'not_sent' && !unsentProven ? planReviewSentWithoutGateSegment() : ''}`;
1070
+ const tail = planReviewOutcomeTail(taskId, decision, effect, outcomeCause);
1034
1071
  const prompt = `<plan-review-outcome>\n${outcome}\n${tail}\n</plan-review-outcome>`;
1035
1072
  const onOutcome = opts?.onOutcome;
1036
1073
  settle(effect === 'not_sent' || effect === 'still_parked');
1037
1074
  try {
1038
- const delivered = enqueuePlanReviewOutcome(prompt, { taskId, dispatchNo, decision: semanticDecision, effect, ...(effect === 'still_parked' && stillParkedCause !== undefined ? { cause: stillParkedCause } : {}), ...(cardId !== undefined ? { questionId: cardId } : {}) });
1075
+ const delivered = enqueuePlanReviewOutcome(prompt, { taskId, dispatchNo, decision: semanticDecision, effect, ...(outcomeCause !== undefined ? { cause: outcomeCause } : {}), ...(cardId !== undefined ? { questionId: cardId } : {}) });
1039
1076
  if (!delivered) {
1040
1077
  if (onOutcome !== undefined) {
1041
1078
  hostLog('debug', `planReviewWire: outcome queue port not installed for task ${taskId} — outcome handed to the host's onOutcome callback only`);
@@ -1054,7 +1091,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
1054
1091
  dispatchNo,
1055
1092
  decision: semanticDecision,
1056
1093
  effect,
1057
- ...(effect === 'still_parked' && stillParkedCause !== undefined ? { cause: stillParkedCause } : {}),
1094
+ ...(outcomeCause !== undefined ? { cause: outcomeCause } : {}),
1058
1095
  ...(cardId !== undefined ? { questionId: cardId } : {}),
1059
1096
  detail: outcome,
1060
1097
  tail,
@@ -1,6 +1,7 @@
1
1
  import { SCREEN_CARD_ORIGINS } from './gateIdentity.js';
2
2
  import { convergeEndedEngineGeneration, } from '../engineGenerationConvergence.js';
3
3
  import { checkpointRowVerdictOf } from '../checkpointRowVerdict.js';
4
+ import { CHECKPOINT_INVALID_OUTCOME, CHECKPOINT_UNSUPPORTED_VERSION } from '../engineErrorCodes.js';
4
5
  import { planReviewCardFactsFrom } from '../planReviewCardFacts.js';
5
6
  import { slotReadOf } from '../wireFailureShape.js';
6
7
  import { PARK_GATE_KEY } from './screenCardCarriers.js';
@@ -291,8 +292,8 @@ function deepFreeze(v) {
291
292
  }
292
293
  return v;
293
294
  }
294
- const GOLD_LACKING = { kind: 'lacking', code: 'checkpoint.unsupported_version', reason: 'version_newer', sentence: 'Newer engine.' };
295
- const GOLD_DAMAGED = { kind: 'damaged', code: 'checkpoint.invalid_outcome', reason: 'requirements_damaged', sentence: 'Row damaged.' };
295
+ const GOLD_LACKING = { kind: 'lacking', code: CHECKPOINT_UNSUPPORTED_VERSION, reason: 'version_newer', sentence: 'Newer engine.' };
296
+ const GOLD_DAMAGED = { kind: 'damaged', code: CHECKPOINT_INVALID_OUTCOME, reason: 'requirements_damaged', sentence: 'Row damaged.' };
296
297
  function goldenCases() {
297
298
  const ALL = ['park-approval', 'park-question', 'plan-review', 'host-reopen-question'];
298
299
  const ready = (name, cards, rows, pick, over = {}) => {
@@ -3,7 +3,7 @@ import { wireFailureShapeOf } from '../wireFailureShape.js';
3
3
  import { hostLog } from '../host.js';
4
4
  import { msg, renderMessage } from '../i18n/translate.js';
5
5
  import { ruleToolNameVerdictOf } from '../protocolSegment.js';
6
- import { RULES_INERT_ENTRY, RULES_INVALID_SHAPE, isRulesRefusalCode } from '../engineErrorCodes.js';
6
+ import { CORRUPT_ROW_ERROR_CODE, RULES_INERT_ENTRY, RULES_INVALID_SHAPE, isRulesRefusalCode } from '../engineErrorCodes.js';
7
7
  import { deniedEntryToolName } from '../engineToolDirectory.js';
8
8
  import { rosterMountedNames } from '../rosterMountedNames.js';
9
9
  import { CORE_ALLOW_ENTRY_INERT_REASONS, CORE_RULE_TOOL_NAME_VERDICTS } from '../generated/engineFactTables.js';
@@ -64,7 +64,7 @@ export function classifySessionPolicyFailure(e) {
64
64
  }
65
65
  if (errorCode === 'loosen_forbidden')
66
66
  return { kind: 'loosen-forbidden', message };
67
- if (errorCode === 'corrupt')
67
+ if (errorCode === CORRUPT_ROW_ERROR_CODE)
68
68
  return { kind: 'store-corrupt', message };
69
69
  if (errorCode === 'conflict')
70
70
  return { kind: 'cas-conflict', message };
@@ -1,6 +1,6 @@
1
1
  import { type PermissionsKeptReason } from '../permissionsDeclaration.js';
2
2
  import type { PermissionsAllowGrantReading } from '../permissionsAllowGrantCapability.js';
3
- import { type LocalAllowOfferedFacts } from './localAllowRedemption.js';
3
+ import { type LocalAllowDeclaredFacts, type LocalAllowOfferedFacts, type LocalAllowRedeemability } from './localAllowRedemption.js';
4
4
  export type StandingRowWhy = 'given' | 'card_unreadable' | 'remember_refused' | 'remember_unreadable' | 'route_cannot_carry' | 'classes_unreadable' | 'not_deployment_list_question' | 'evidence_unreadable' | 'offers_unreadable' | 'engine_lane_closed' | 'args_unavailable' | 'grant_binding' | 'tool_name_unreadable' | 'rule_cannot_clear' | 'host_fact_missing' | 'card_face_absent' | 'face_not_generic' | 'rule_not_sent' | 'redemption_closed' | 'redemption_unknown';
5
5
  export type StandingPersistentRow = 'engine_offer' | 'local_whole_tool' | 'none';
6
6
  export interface StandingRowsWhy {
@@ -17,6 +17,7 @@ export interface StandingRows {
17
17
  export interface StandingRowsHostFacts {
18
18
  readonly wholeToolRuleSent?: boolean;
19
19
  readonly readOnlyRedemption?: LocalAllowOfferedFacts;
20
+ readonly wholeToolLanding?: LocalAllowDeclaredFacts;
20
21
  }
21
22
  export declare function standingRowsOf(card: object | null | undefined, host?: StandingRowsHostFacts | null): StandingRows;
22
23
  export declare function seatSessionRowOf(card: object | null | undefined, requestId: string | undefined): boolean;
@@ -31,6 +32,7 @@ export declare function rememberOfferable(card: object | null | undefined, host?
31
32
  export interface LocalAllowRuleEnforcement {
32
33
  readonly enforceable: boolean;
33
34
  readonly engine: 'sent' | 'kept' | 'unreadable';
35
+ readonly redemption: LocalAllowRedeemability;
34
36
  readonly keptReason?: PermissionsKeptReason;
35
37
  }
36
- export declare function localAllowRuleEnforceable(rule: string, caps: PermissionsAllowGrantReading): LocalAllowRuleEnforcement;
38
+ export declare function localAllowRuleEnforceable(rule: string, caps: PermissionsAllowGrantReading, landing?: LocalAllowDeclaredFacts): LocalAllowRuleEnforcement;
@@ -6,7 +6,7 @@ import { inSlotReadOf, inValueOf } from '../wireFailureShape.js';
6
6
  import { toolPermissionRequestIdDomain } from '../seatContract.js';
7
7
  import { permissionsDeclarationReport } from '../permissionsDeclaration.js';
8
8
  import { readCardUnreadableEvidence, sessionAllowCardReadable, sessionAllowEligible } from './sessionAllowRoute.js';
9
- import { localAllowRedeemability } from './localAllowRedemption.js';
9
+ import { localAllowLandingRedeemability, localAllowRedeemability } from './localAllowRedemption.js';
10
10
  const UNREADABLE = Symbol('unreadable');
11
11
  function ownData(v, key) {
12
12
  try {
@@ -103,6 +103,11 @@ function persistentRowOf(card, host, evidenceUnreadable) {
103
103
  return { row: 'none', why: 'host_fact_missing' };
104
104
  if (!sent)
105
105
  return { row: 'none', why: 'rule_not_sent' };
106
+ const judged = localAllowLandingRedeemability(tool, ownData(host, 'wholeToolLanding'));
107
+ if (judged.kind === 'not_redeemable')
108
+ return { row: 'none', why: 'redemption_closed' };
109
+ if (judged.kind === 'unknown')
110
+ return { row: 'none', why: 'redemption_unknown' };
106
111
  return { row: 'local_whole_tool', why: 'given' };
107
112
  }
108
113
  function enginePremiseOf(card) {
@@ -214,7 +219,7 @@ export function rememberOfferable(card, host) {
214
219
  deploymentList: SESSION_OFFER[rows.why.deploymentList],
215
220
  });
216
221
  }
217
- export function localAllowRuleEnforceable(rule, caps) {
222
+ export function localAllowRuleEnforceable(rule, caps, landing) {
218
223
  let engine = 'unreadable';
219
224
  let keptReason;
220
225
  if (typeof rule === 'string' && rule.trim() !== '') {
@@ -234,9 +239,11 @@ export function localAllowRuleEnforceable(rule, caps) {
234
239
  engine = 'unreadable';
235
240
  }
236
241
  }
242
+ const redemption = localAllowLandingRedeemability(rule, landing);
237
243
  return Object.freeze({
238
- enforceable: engine === 'sent',
244
+ enforceable: engine === 'sent' && redemption.kind === 'redeemable',
239
245
  engine,
246
+ redemption,
240
247
  ...(keptReason !== undefined ? { keptReason } : {}),
241
248
  });
242
249
  }
@@ -1,6 +1,6 @@
1
1
  import { engineVersionTier } from './supportFloor.js';
2
2
  const HOOKS_READER_GENERATIONS = Object.freeze([
3
- Object.freeze({ from: '7.139.0', below: '7.147.0', notRead: Object.freeze(['onFailure']) }),
3
+ Object.freeze({ from: '7.139.0', below: '7.147.0', notRead: Object.freeze(['onFailure', 'rewakeMessage', 'rewakeSummary', 'cloud', 'continueOnBlock']) }),
4
4
  ]);
5
5
  const HOOKS_READER_FAIL_CLOSED = Object.freeze([...new Set(HOOKS_READER_GENERATIONS.flatMap((g) => g.notRead))]);
6
6
  export function hooksReaderNotReadKeys(serverVersion) {