@sema-agent/client-core 0.112.0 → 0.113.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.
- package/CHANGELOG.md +153 -0
- package/README.md +37 -24
- package/dist/adapt/arms.js +7 -1
- package/dist/adapt/textStream.d.ts +2 -0
- package/dist/adapt/textStream.js +27 -1
- package/dist/adapter/activeRunSelfHeal.d.ts +7 -5
- package/dist/adapter/activeRunSelfHeal.js +38 -66
- package/dist/adapter/downstream/eventToSdkMessage.js +4 -0
- package/dist/adapter/downstream/terminalToSdkResult.d.ts +8 -0
- package/dist/adapter/downstream/terminalToSdkResult.js +36 -1
- package/dist/adapter/runStream.d.ts +2 -12
- package/dist/adapter/runStream.js +46 -19
- package/dist/agentSession/engineAgentRegistryRead.js +2 -1
- package/dist/agentSession/engineSessionBackgroundStop.js +2 -2
- package/dist/attachmentOutcome.d.ts +35 -0
- package/dist/attachmentOutcome.js +192 -0
- package/dist/attachmentPathPlan.d.ts +67 -0
- package/dist/attachmentPathPlan.js +203 -0
- package/dist/attachmentRoute.d.ts +9 -1
- package/dist/attachmentRoute.js +23 -2
- package/dist/declared/supportFloorDeclared.d.ts +1 -1
- package/dist/declared/supportFloorDeclared.js +1 -0
- package/dist/deviceRebind.js +2 -3
- package/dist/displayUntrusted.js +126 -19
- package/dist/engineAddress.d.ts +1 -1
- package/dist/engineAddress.js +9 -6
- package/dist/engineErrorCodes.d.ts +3 -0
- package/dist/engineErrorCodes.js +4 -0
- package/dist/engineNoticeCodes.d.ts +34 -4
- package/dist/engineNoticeCodes.js +133 -6
- package/dist/engineUnavailable.d.ts +4 -2
- package/dist/engineUnavailable.js +99 -17
- package/dist/fleet/fleetLedger.d.ts +9 -3
- package/dist/fleet/fleetLedger.js +112 -16
- package/dist/fleet/fleetProjection.d.ts +5 -0
- package/dist/fleet/fleetProjection.js +9 -1
- package/dist/gateVocabulary.d.ts +7 -0
- package/dist/gateVocabulary.js +40 -14
- package/dist/generated/engineFactTables.d.ts +4 -2
- package/dist/generated/engineFactTables.js +198 -2
- package/dist/generated/engineNoticeTables.js +56 -8
- package/dist/generated/settingsSchemaFns.d.ts +7 -0
- package/dist/generated/settingsSchemaFns.js +11 -0
- package/dist/generated/settingsSchemaHash.d.ts +1 -0
- package/dist/generated/settingsSchemaHash.js +58 -0
- package/dist/generated/settingsSchemaOwnKeyRead.js +6 -1
- package/dist/generated/settingsSchemaTables.d.ts +3 -2
- package/dist/generated/settingsSchemaTables.js +14 -0
- package/dist/generated/settingsSchemaTightenVerdict.d.ts +1 -0
- package/dist/generated/settingsSchemaTightenVerdict.js +125 -0
- package/dist/headlessPermissionModeWire.d.ts +7 -2
- package/dist/headlessPermissionModeWire.js +77 -8
- package/dist/hitl/approvalDecisionDoor.d.ts +24 -0
- package/dist/hitl/approvalDecisionDoor.js +77 -0
- package/dist/hitl/approvalDelivery.d.ts +6 -0
- package/dist/hitl/approvalDelivery.js +57 -7
- package/dist/hitl/approvalResolution.d.ts +6 -3
- package/dist/hitl/approvalResolution.js +24 -2
- package/dist/hitl/approvalsFeed.js +1 -1
- package/dist/hitl/automaticAnswerVerdict.d.ts +1 -1
- package/dist/hitl/decisionSend.d.ts +7 -4
- package/dist/hitl/decisionSend.js +90 -41
- package/dist/hitl/frameRouter.d.ts +3 -3
- package/dist/hitl/frameRouter.js +5 -0
- package/dist/hitl/hitlBridge.d.ts +1 -0
- package/dist/hitl/hitlBridge.js +17 -4
- package/dist/hitl/hitlHostSurface.d.ts +3 -2
- package/dist/hitl/hitlHostSurface.js +5 -3
- package/dist/hitl/legacyApprovalShapes.d.ts +142 -0
- package/dist/hitl/legacyApprovalShapes.js +1 -0
- package/dist/hitl/livePendingAsk.d.ts +4 -4
- package/dist/hitl/livePendingAsk.js +5 -3
- package/dist/hitl/parkResolver.js +1 -1
- package/dist/hitl/persistedRulesWire.js +2 -2
- package/dist/hitl/planReviewCard.d.ts +4 -2
- package/dist/hitl/planReviewCard.js +66 -1
- package/dist/hitl/planReviewWire.d.ts +34 -2
- package/dist/hitl/planReviewWire.js +326 -150
- package/dist/hitl/sessionPolicyWire.js +16 -7
- package/dist/hitl/subagentSource.d.ts +1 -0
- package/dist/hitl/subagentSource.js +11 -0
- package/dist/hitl/toolApprovalWire.d.ts +34 -10
- package/dist/hitl/toolApprovalWire.js +182 -33
- package/dist/hookMatcherVerdict.d.ts +2 -1
- package/dist/hookMatcherVerdict.js +3 -2
- package/dist/hooksReaderGenerations.d.ts +8 -0
- package/dist/hooksReaderGenerations.js +49 -0
- package/dist/hooksWireCaps.js +43 -18
- package/dist/hostSettingsSource.d.ts +1 -1
- package/dist/hostSettingsSource.js +2 -2
- package/dist/hostTurnSettings.d.ts +2 -2
- package/dist/hostTurnSettings.js +87 -47
- package/dist/humanTurn.d.ts +2 -1
- package/dist/humanTurn.js +44 -13
- package/dist/humanTurnRead.d.ts +7 -0
- package/dist/humanTurnRead.js +23 -14
- package/dist/humanTurnSentences.js +3 -0
- package/dist/i18n/messages.en.d.ts +93 -5
- package/dist/i18n/messages.en.js +100 -12
- package/dist/i18n/messages.zh.js +110 -15
- package/dist/index.d.ts +29 -15
- package/dist/index.js +17 -9
- package/dist/liveModelCatalog.js +1 -0
- package/dist/liveQuestionStore.d.ts +2 -0
- package/dist/liveQuestionStore.js +7 -2
- package/dist/localConfig/kernel.d.ts +2 -1
- package/dist/localConfig/kernel.js +12 -18
- package/dist/localConfig/mcp/edit.js +25 -27
- package/dist/localConfig/mcp/servers.js +5 -8
- package/dist/localConfig/modelPool.d.ts +6 -1
- package/dist/localConfig/modelPool.js +104 -27
- package/dist/localConfig/modelPoolWords.d.ts +2 -0
- package/dist/localConfig/modelPoolWords.js +1 -0
- package/dist/localConfig/settingsWrite.js +1 -14
- package/dist/localConfig/skills.js +9 -16
- package/dist/localEngineSpawn.d.ts +29 -1
- package/dist/localEngineSpawn.js +62 -1
- package/dist/mcpLiveness.d.ts +1 -1
- package/dist/mcpProbeWire.js +2 -2
- package/dist/mcpReconnect.js +2 -2
- package/dist/model/catalogLoader.js +3 -17
- package/dist/model/onboardJudgement.d.ts +8 -0
- package/dist/model/onboardJudgement.js +8 -3
- package/dist/model/onboardSteps.d.ts +19 -0
- package/dist/model/onboardSteps.js +76 -0
- package/dist/notifications.js +20 -12
- package/dist/ownKey.d.ts +4 -0
- package/dist/ownKey.js +61 -0
- package/dist/pathShape.d.ts +14 -0
- package/dist/pathShape.js +44 -0
- package/dist/planReviewCardFacts.d.ts +3 -0
- package/dist/planReviewCardFacts.js +20 -3
- package/dist/planReviewOutcomeCause.d.ts +2 -0
- package/dist/planReviewOutcomeCause.js +12 -0
- package/dist/pluginHooksWire.d.ts +20 -8
- package/dist/pluginHooksWire.js +204 -86
- package/dist/prepareRefusal.d.ts +2 -0
- package/dist/prepareRefusal.js +11 -1
- package/dist/request/taskRequest.js +3 -1
- package/dist/resultFrameKeys.d.ts +1 -0
- package/dist/resultFrameKeys.js +3 -0
- package/dist/resumeRefusalCopy.js +3 -1
- package/dist/retryStatus.d.ts +11 -1
- package/dist/retryStatus.js +15 -4
- package/dist/runTerminal.d.ts +1 -0
- package/dist/runTerminal.js +15 -1
- package/dist/sdkWireTransit.d.ts +1 -1
- package/dist/seatContract.d.ts +12 -3
- package/dist/seatContract.js +24 -13
- package/dist/sessionEngine.js +1 -1
- package/dist/sessionHooksRoster.js +8 -6
- package/dist/sessionIdSegment.d.ts +0 -1
- package/dist/sessionIdSegment.js +1 -3
- package/dist/sessionMemoryErase.js +2 -2
- package/dist/sessionMemoryStatus.js +2 -1
- package/dist/sessionOccupant.d.ts +29 -0
- package/dist/sessionOccupant.js +78 -0
- package/dist/settingsHooksSalvage.d.ts +4 -1
- package/dist/settingsHooksSalvage.js +22 -9
- package/dist/skillOverrideChain.d.ts +3 -0
- package/dist/skillOverrideChain.js +31 -4
- package/dist/skillsWireCaps.d.ts +16 -2
- package/dist/skillsWireCaps.js +47 -20
- package/dist/subagent/agentStateDetail.js +32 -8
- package/dist/subagent/engineCompactWire.js +48 -36
- package/dist/subagent/engineDelegatedPrompt.d.ts +3 -2
- package/dist/subagent/engineDelegatedPrompt.js +65 -28
- package/dist/subagentEditOutcomes.d.ts +9 -0
- package/dist/subagentEditOutcomes.js +43 -0
- package/dist/supportFloor.d.ts +2 -1
- package/dist/supportFloor.js +1 -1
- package/dist/toolResult.js +4 -2
- package/dist/toolRoster.d.ts +1 -0
- package/dist/toolRoster.js +1 -0
- package/dist/usageAnalytics.js +2 -1
- package/dist/userAttachmentEnvelope.d.ts +11 -0
- package/dist/userAttachmentEnvelope.js +7 -2
- package/dist/userAttachmentSeat.d.ts +7 -1
- package/dist/userAttachmentSeat.js +44 -7
- package/dist/wiringInstructionsAttribution.js +8 -2
- package/docs/INTEGRATION-CLIENTS.md +1654 -16
- package/package.json +5 -5
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { publishQuestionFrame, publishQuestionFrameFor, registerLocalQuestionResponder, unregisterLocalQuestionResponder, hasLocalQuestionResponder, hasQuestionOverlayFor, } from '../liveQuestionStore.js';
|
|
1
|
+
import { deliverQuestionFrameFor, publishQuestionFrame, publishQuestionFrameFor, registerLocalQuestionResponder, unregisterLocalQuestionResponder, hasLocalQuestionResponder, hasQuestionOverlayFor, } from '../liveQuestionStore.js';
|
|
2
2
|
import { hostLog } from '../host.js';
|
|
3
|
+
import { displayUntrusted } from '../displayUntrusted.js';
|
|
3
4
|
import { invokeHostCallbackIsolated } from '../hostCallbackGuard.js';
|
|
4
5
|
import { abortableSleep, settleWithinDeadline } from '../abortableSleep.js';
|
|
5
6
|
import { clientHasVerbs, engineWireFor, noAnswerKindOf, sendOnce } from '../engineWireFor.js';
|
|
@@ -8,16 +9,18 @@ import { enqueuePlanReviewOutcome } from '../notifications.js';
|
|
|
8
9
|
import { corruptStoredRowContent, corruptStoredRowWhere } from '../wireErrorTriage.js';
|
|
9
10
|
import { decideFailureNote } from '../decideFailureNote.js';
|
|
10
11
|
import { readThrownExtra, wireFailureTextsOf } from '../wireFailureShape.js';
|
|
11
|
-
import { appendOwnElement, readOwnDataNonEmptyString, readOwnDataValue } from '../ownKey.js';
|
|
12
|
+
import { appendOwnElement, guardedRead, hostPresenceOf, readOwnDataNonEmptyString, readOwnDataValue } from '../ownKey.js';
|
|
12
13
|
import { checkpointUnsupportedVersionContent, checkpointUnsupportedVersionFromError, resumeContextUnavailableContent, resumeContextUnavailableFromError } from '../resumeRefusalCopy.js';
|
|
13
14
|
import { APPROVAL_STALE, REQUEST_FIELD_CONFLICT } from '../engineErrorCodes.js';
|
|
14
|
-
import { refusalDeliveryOf } from './decisionRefusal.js';
|
|
15
|
+
import { readDecisionRefusalReading, refusalDeliveryOf } from './decisionRefusal.js';
|
|
16
|
+
import { decisionRefusalWayOutContent } from '../checkpointRowVerdict.js';
|
|
15
17
|
import { directFailureVerdictOf, directSafetyStopOf } from './approvalResolution.js';
|
|
16
18
|
import { __resetDecisionLedgerForTests, decisionInFlightOf, decisionKeyInFlight, decisionLedgerKeysOf, decisionOpsWithKey, decisionReadMark, dropDecisionOp, latestDecisionHandedMark, markLatestDecisionNotApplied, openDecisionOp, planDecisionKey, readMarkOf, settleDecisionOp, settleDecisionOpsOnGateLeft, } from './decisionLedger.js';
|
|
17
19
|
import { denyReasonForWire, isPlanReviewModeAfter } from './hitlBridge.js';
|
|
18
20
|
import { freshPlanReviewQuestionId, planReviewQuestionId, REOPEN_ID_TAIL, taskIdFromPlanReviewQuestionId } from './gateIdentity.js';
|
|
19
|
-
import { isPlanReviewPark, mintPlanReviewCardFacts, notePlanReviewCardIssued, pausedCheckpointIdOf, planReviewCardFactsFrom, planReviewFactsCurrent, registeredPlanReviewCardFacts, samePlanReviewCardFacts, unknownBody, _resetPlanReviewRunsForTest, } from '../planReviewCardFacts.js';
|
|
21
|
+
import { isPlanReviewPark, mintPlanReviewCardFacts, noteCardGateHandedOver, notePlanReviewCardIssued, pausedCheckpointIdOf, planReviewCardFactsFrom, planReviewCardReplacesEarlier, planReviewFactsCurrent, registeredPlanReviewCardFacts, samePlanReviewCardFacts, unknownBody, _resetPlanReviewRunsForTest, } from '../planReviewCardFacts.js';
|
|
20
22
|
import { planReviewAskAgainKey, planReviewCardNoteRef, planReviewDecisionReasonRef, planReviewUnreadableNoteRef, takePlanReviewFacts, _resetPlanReviewFactsFlightsForTest, } from './planReviewCard.js';
|
|
23
|
+
import { planReviewOutcomeCauseOf } from '../planReviewOutcomeCause.js';
|
|
21
24
|
import { EN_MESSAGES } from '../i18n/messages.en.js';
|
|
22
25
|
import { msg, renderMessage, DATA_FACE_LANGUAGE } from '../i18n/translate.js';
|
|
23
26
|
import { notePlanReviewAnswered, notePlanReviewAnsweredFor, planReviewArmedKeyFor, waitForGateArmedFor, wasGateArmedFor, } from './armedGateRegistry.js';
|
|
@@ -132,6 +135,14 @@ function cardShapeOf(facts, submittedInPlanMode) {
|
|
|
132
135
|
}
|
|
133
136
|
return submittedInPlanMode ? 'three_choice' : 'two_choice';
|
|
134
137
|
}
|
|
138
|
+
const PLAN_REVIEW_HEADING_GATE_MAX = 120;
|
|
139
|
+
function planReviewHeadingOf(facts, replaces, lang) {
|
|
140
|
+
const gate = facts?.checkpointId === undefined ? '' : displayUntrusted(facts.checkpointId, { max: PLAN_REVIEW_HEADING_GATE_MAX });
|
|
141
|
+
if (gate === '')
|
|
142
|
+
return renderMessage(msg('approval.planReview.heading.plain'), lang);
|
|
143
|
+
const head = msg('approval.planReview.heading.gate', { gate });
|
|
144
|
+
return renderMessage(replaces ? msg('approval.planReview.heading.replaces', { head }) : head, lang);
|
|
145
|
+
}
|
|
135
146
|
export function planReviewCardViewOf(frame, lang) {
|
|
136
147
|
try {
|
|
137
148
|
const questionId = readOwnDataNonEmptyString(frame ?? undefined, 'questionId');
|
|
@@ -140,14 +151,20 @@ export function planReviewCardViewOf(frame, lang) {
|
|
|
140
151
|
return undefined;
|
|
141
152
|
const facts = planReviewCardFactsFrom(readOwnDataValue(frame ?? undefined, '_sema_planReview'));
|
|
142
153
|
if (facts === undefined || facts.taskId !== taskId) {
|
|
143
|
-
return Object.freeze({ options: OPTIONS_BY_SHAPE.withheld, note: renderMessage(planReviewUnreadableNoteRef(), lang) });
|
|
154
|
+
return Object.freeze({ options: OPTIONS_BY_SHAPE.withheld, note: renderMessage(planReviewUnreadableNoteRef(), lang), heading: planReviewHeadingOf(undefined, false, lang) });
|
|
144
155
|
}
|
|
145
156
|
const shape = cardShapeOf(facts, frame !== null && frame !== undefined && offeredModeFromLabels(frame) === 'three_choice');
|
|
146
157
|
const noteRef = planReviewCardNoteRef(facts);
|
|
147
|
-
|
|
158
|
+
const replaces = readOwnDataValue(frame ?? undefined, '_sema_planReviewReplaces') === true;
|
|
159
|
+
return Object.freeze({
|
|
160
|
+
facts,
|
|
161
|
+
options: OPTIONS_BY_SHAPE[shape],
|
|
162
|
+
...(noteRef !== undefined ? { note: renderMessage(noteRef, lang) } : {}),
|
|
163
|
+
heading: planReviewHeadingOf(facts, replaces, lang),
|
|
164
|
+
});
|
|
148
165
|
}
|
|
149
166
|
catch {
|
|
150
|
-
return Object.freeze({ options: OPTIONS_BY_SHAPE.withheld, note: renderMessage(planReviewUnreadableNoteRef(), lang) });
|
|
167
|
+
return Object.freeze({ options: OPTIONS_BY_SHAPE.withheld, note: renderMessage(planReviewUnreadableNoteRef(), lang), heading: planReviewHeadingOf(undefined, false, lang) });
|
|
151
168
|
}
|
|
152
169
|
}
|
|
153
170
|
export function planReviewEvidenceMovedOf(shown, latest) {
|
|
@@ -304,7 +321,7 @@ function planReviewQuestions(question, shape) {
|
|
|
304
321
|
},
|
|
305
322
|
];
|
|
306
323
|
}
|
|
307
|
-
function planReviewQuestionFrame(questionId, question, shape, facts) {
|
|
324
|
+
function planReviewQuestionFrame(questionId, question, shape, facts, sessionKey = DEFAULT_SESSION_KEY) {
|
|
308
325
|
offeredModeByQuestionId.delete(questionId);
|
|
309
326
|
offeredModeByQuestionId.set(questionId, shape);
|
|
310
327
|
while (offeredModeByQuestionId.size > OFFERED_MODE_LEDGER_MAX) {
|
|
@@ -318,10 +335,17 @@ function planReviewQuestionFrame(questionId, question, shape, facts) {
|
|
|
318
335
|
questionId,
|
|
319
336
|
questions: planReviewQuestions(question, shape),
|
|
320
337
|
...(facts !== undefined ? { _sema_planReview: facts } : {}),
|
|
338
|
+
...(facts !== undefined && planReviewCardReplacesEarlier(sessionKey, facts) ? { _sema_planReviewReplaces: true } : {}),
|
|
321
339
|
};
|
|
322
340
|
}
|
|
341
|
+
function deliverPlanReviewCard(sessionKey, frame, facts) {
|
|
342
|
+
if (deliverQuestionFrameFor(sessionKey, frame) !== 'delivered')
|
|
343
|
+
return;
|
|
344
|
+
if (facts !== undefined)
|
|
345
|
+
noteCardGateHandedOver(sessionKey, facts.taskId, facts.checkpointId);
|
|
346
|
+
}
|
|
323
347
|
function publishArmedPlanReviewCard(inst) {
|
|
324
|
-
|
|
348
|
+
deliverPlanReviewCard(DEFAULT_SESSION_KEY, planReviewQuestionFrame(inst.questionId, ARM_QUESTION, inst.shape, inst.facts, DEFAULT_SESSION_KEY), inst.facts);
|
|
325
349
|
}
|
|
326
350
|
function retireArmedPlanReview(taskId, inst) {
|
|
327
351
|
if (armedPlanReviews.get(taskId) === inst)
|
|
@@ -445,9 +469,7 @@ export function armPlanReviewApproval(result, sessionKey, opts) {
|
|
|
445
469
|
else if (!engineWireTarget()) {
|
|
446
470
|
return false;
|
|
447
471
|
}
|
|
448
|
-
const delivery = wire
|
|
449
|
-
? { ...(wire !== undefined ? { wire } : {}), ...(opts?.onOutcome !== undefined ? { onOutcome: opts.onOutcome } : {}) }
|
|
450
|
-
: undefined;
|
|
472
|
+
const delivery = planReviewDeliveryFrom(wire, opts?.onOutcome, opts?.onAccepted);
|
|
451
473
|
const { taskId } = result;
|
|
452
474
|
const gateBefore = pausedCheckpointIdOf(result);
|
|
453
475
|
if (gateBefore === undefined) {
|
|
@@ -463,12 +485,12 @@ export function armPlanReviewApproval(result, sessionKey, opts) {
|
|
|
463
485
|
void taking.pending.then((taken) => {
|
|
464
486
|
try {
|
|
465
487
|
if (taken !== undefined && planReviewFactsCurrent(taken)) {
|
|
466
|
-
|
|
488
|
+
presentAfterAwait(taken.facts, delivery);
|
|
467
489
|
return;
|
|
468
490
|
}
|
|
469
491
|
const current = registeredPlanReviewCardFacts(DEFAULT_SESSION_KEY, taskId, gateBefore);
|
|
470
492
|
if (current !== undefined) {
|
|
471
|
-
|
|
493
|
+
presentAfterAwait(current, delivery);
|
|
472
494
|
return;
|
|
473
495
|
}
|
|
474
496
|
hostLog('debug', `planReviewWire: arm-superseded task=${taskId}`);
|
|
@@ -487,7 +509,17 @@ export function armPlanReviewApproval(result, sessionKey, opts) {
|
|
|
487
509
|
hostLog('debug', `planReviewWire: arm failed (fail-soft): ${String(e)}`);
|
|
488
510
|
return false;
|
|
489
511
|
}
|
|
490
|
-
function
|
|
512
|
+
function presentAfterAwait(facts, delivery) {
|
|
513
|
+
if (!isPlanReviewPark(result))
|
|
514
|
+
return;
|
|
515
|
+
const presence = publishPresenceOf(opts);
|
|
516
|
+
if (presence !== 'here') {
|
|
517
|
+
hostLog('debug', `planReviewWire: arm-withheld-${presence} task=${result.taskId}`);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
presentArmedPlanReview(facts, delivery, () => publishPresenceOf(opts) === 'here');
|
|
521
|
+
}
|
|
522
|
+
function presentArmedPlanReview(facts, delivery, recheckHere) {
|
|
491
523
|
if (!isPlanReviewPark(result))
|
|
492
524
|
return;
|
|
493
525
|
const { taskId } = result;
|
|
@@ -501,6 +533,10 @@ export function armPlanReviewApproval(result, sessionKey, opts) {
|
|
|
501
533
|
prev.checkpointId !== undefined &&
|
|
502
534
|
prev.checkpointId === gateBefore &&
|
|
503
535
|
samePlanReviewCardFacts(prev.facts, facts)) {
|
|
536
|
+
if (recheckHere !== undefined && !recheckHere()) {
|
|
537
|
+
hostLog('debug', `planReviewWire: arm-withheld-after-retire task=${taskId}`);
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
504
540
|
hostLog('debug', `planReviewWire: arm replayed — re-presenting the still-armed card for task ${taskId}`);
|
|
505
541
|
publishArmedPlanReviewCard(prev);
|
|
506
542
|
return;
|
|
@@ -508,6 +544,10 @@ export function armPlanReviewApproval(result, sessionKey, opts) {
|
|
|
508
544
|
hostLog('debug', `planReviewWire: arm-retire task=${taskId} card=${prev.questionId}`);
|
|
509
545
|
retireArmedPlanReview(taskId, prev);
|
|
510
546
|
}
|
|
547
|
+
if (recheckHere !== undefined && !recheckHere()) {
|
|
548
|
+
hostLog('debug', `planReviewWire: arm-withheld-after-retire task=${taskId}`);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
511
551
|
const issuedBefore = notePlanReviewCardIssued(DEFAULT_SESSION_KEY, taskId);
|
|
512
552
|
const questionId = issuedBefore || hadArmedInstance ? freshPlanReviewQuestionId(taskId, reopenIdSuffix()) : planReviewQuestionId(taskId);
|
|
513
553
|
const instance = Object.freeze({ questionId, ...(gateBefore !== undefined ? { checkpointId: gateBefore } : {}), facts, shape });
|
|
@@ -569,6 +609,11 @@ export function planReviewOutcomeTail(taskId, decision, effect, cause) {
|
|
|
569
609
|
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.`;
|
|
570
610
|
}
|
|
571
611
|
}
|
|
612
|
+
function planReviewDeliveryFrom(wire, onOutcome, onAccepted) {
|
|
613
|
+
if (wire === undefined && onOutcome === undefined && onAccepted === undefined)
|
|
614
|
+
return undefined;
|
|
615
|
+
return { ...(wire !== undefined ? { wire } : {}), ...(onOutcome !== undefined ? { onOutcome } : {}), ...(onAccepted !== undefined ? { onAccepted } : {}) };
|
|
616
|
+
}
|
|
572
617
|
function planReviewDecisionTimedOutOutcome(decision) {
|
|
573
618
|
return (`The plan_review ${decision} was sent, but no answer came back from the engine within the time limit this host allows for it — ` +
|
|
574
619
|
`it may have taken effect (${decision === 'approve' ? 'an approval keeps the engine busy until the resumed work finishes' : 'the engine may still be applying it'}).`);
|
|
@@ -666,11 +711,34 @@ function noteEngineRefusedGate(id) {
|
|
|
666
711
|
engineRefusedGateIds.delete(oldest.value);
|
|
667
712
|
}
|
|
668
713
|
}
|
|
714
|
+
function wireGateOf(decision, gate) {
|
|
715
|
+
if (gate === undefined)
|
|
716
|
+
return undefined;
|
|
717
|
+
if (decision === 'approve')
|
|
718
|
+
return gate;
|
|
719
|
+
return engineRefusedGateIds.has(gate) ? undefined : gate;
|
|
720
|
+
}
|
|
721
|
+
function planReviewWireBodyOf(decision, reason, mode, gate) {
|
|
722
|
+
const withReason = reason !== undefined ? { reason } : {};
|
|
723
|
+
const withMode = mode !== undefined ? { permissionModeAfter: mode } : {};
|
|
724
|
+
if (decision === 'approve')
|
|
725
|
+
return gate === undefined ? undefined : Object.freeze({ decision, ...withReason, ...withMode, checkpointId: gate });
|
|
726
|
+
return Object.freeze({ decision, ...withReason, ...withMode, ...(gate !== undefined ? { checkpointId: gate } : {}) });
|
|
727
|
+
}
|
|
669
728
|
function planReviewDecisionReasonSegment(reason, card) {
|
|
670
729
|
if (reason === undefined)
|
|
671
730
|
return '';
|
|
672
731
|
try {
|
|
673
|
-
|
|
732
|
+
const ref = planReviewDecisionReasonRef(reason, card);
|
|
733
|
+
return ref === undefined ? '' : ` ${renderMessage(ref, DATA_FACE_LANGUAGE)}`;
|
|
734
|
+
}
|
|
735
|
+
catch {
|
|
736
|
+
return '';
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
function planReviewSentWithoutGateSegment() {
|
|
740
|
+
try {
|
|
741
|
+
return ` ${renderMessage(msg('approval.planReview.decision.sentWithoutGateId'), DATA_FACE_LANGUAGE)}`;
|
|
674
742
|
}
|
|
675
743
|
catch {
|
|
676
744
|
return '';
|
|
@@ -723,13 +791,43 @@ function bodyWordOf(body, key) {
|
|
|
723
791
|
return undefined;
|
|
724
792
|
}
|
|
725
793
|
}
|
|
726
|
-
function
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
794
|
+
function planReviewAcceptanceOf(body) {
|
|
795
|
+
const obj = body !== null && typeof body === 'object' ? body : undefined;
|
|
796
|
+
const str = (key) => (obj === undefined ? undefined : guardedRead(() => readOwnDataNonEmptyString(obj, key)));
|
|
797
|
+
const isTrue = (key) => obj !== undefined && guardedRead(() => readOwnDataValue(obj, key)) === true;
|
|
798
|
+
const taskId = str('taskId');
|
|
799
|
+
const sessionId = str('sessionId');
|
|
800
|
+
const status = str('status');
|
|
801
|
+
const checkpointId = str('checkpointId');
|
|
802
|
+
const engine = Object.freeze({
|
|
803
|
+
...(taskId !== undefined ? { taskId } : {}),
|
|
804
|
+
...(sessionId !== undefined ? { sessionId } : {}),
|
|
805
|
+
...(status !== undefined ? { status } : {}),
|
|
806
|
+
...(isTrue('decisionCommitted') ? { decisionCommitted: true } : {}),
|
|
807
|
+
...(checkpointId !== undefined ? { checkpointId } : {}),
|
|
808
|
+
});
|
|
809
|
+
const errorCode = str('errorCode');
|
|
810
|
+
return Object.freeze({ engine, reparked: isTrue('retriable'), ...(errorCode !== undefined ? { errorCode } : {}) });
|
|
811
|
+
}
|
|
812
|
+
function acceptanceSettled(a) {
|
|
813
|
+
return a !== undefined && a.engine.decisionCommitted === true && !a.reparked;
|
|
814
|
+
}
|
|
815
|
+
function planReviewAcceptedPayloadOf(taskId, dispatchNo, semanticDecision, cardId, a, wireGate, card) {
|
|
816
|
+
const body = card?.body;
|
|
817
|
+
const decided = acceptanceSettled(a) && semanticDecision !== 'ask_again' && wireGate !== undefined && body !== undefined && body.kind === 'shown'
|
|
818
|
+
? Object.freeze({ verdict: semanticDecision === 'approve' ? 'approved' : 'rejected', plan: body.markdown })
|
|
819
|
+
: undefined;
|
|
820
|
+
return Object.freeze({
|
|
821
|
+
taskId,
|
|
822
|
+
dispatchNo,
|
|
823
|
+
decision: semanticDecision,
|
|
824
|
+
...(cardId !== undefined ? { questionId: cardId } : {}),
|
|
825
|
+
engine: a.engine,
|
|
826
|
+
...(decided !== undefined ? { decided } : {}),
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
function planReviewDeliveryOf(a, semanticDecision) {
|
|
830
|
+
return acceptanceSettled(a) && semanticDecision !== 'approve' ? 'follow' : 'submit';
|
|
733
831
|
}
|
|
734
832
|
function askAgainReasonOf(card, opts) {
|
|
735
833
|
try {
|
|
@@ -741,9 +839,17 @@ function askAgainReasonOf(card, opts) {
|
|
|
741
839
|
return renderMessage(msg('approval.planReview.askAgain.unknown'), DATA_FACE_LANGUAGE);
|
|
742
840
|
}
|
|
743
841
|
}
|
|
842
|
+
function planReviewDecisionWordOf(raw) {
|
|
843
|
+
return raw === 'approve' || raw === 'reject' || raw === 'ask_again' ? raw : undefined;
|
|
844
|
+
}
|
|
744
845
|
async function decidePlanReviewFor(taskId, decision, permissionModeAfter, opts, card, cardId) {
|
|
846
|
+
const word = planReviewDecisionWordOf(decision);
|
|
847
|
+
if (word === undefined) {
|
|
848
|
+
hostLog('error', `planReviewWire: decision-word-refused typeof=${typeof decision}`);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
745
851
|
if (typeof taskId === 'string' && planReviewOpsOf(taskId).some((op) => op.phase === 'posting')) {
|
|
746
|
-
hostLog('error', `planReviewWire: decidePlanReview(${
|
|
852
|
+
hostLog('error', `planReviewWire: decidePlanReview(${word}) for task ${taskId} REFUSED — a decision for this task is already in flight (duplicate delivery latched)`);
|
|
747
853
|
return;
|
|
748
854
|
}
|
|
749
855
|
let op = typeof taskId === 'string' ? claimablePlanReviewDecisionOp(taskId) : undefined;
|
|
@@ -770,11 +876,11 @@ async function decidePlanReviewFor(taskId, decision, permissionModeAfter, opts,
|
|
|
770
876
|
settleDecisionOp(mine, 'package');
|
|
771
877
|
};
|
|
772
878
|
const dispatchNo = ++decideDispatchSeq;
|
|
773
|
-
hostLog('debug', `planReviewWire: decidePlanReview dispatch #${dispatchNo} task=${taskId} decision=${
|
|
879
|
+
hostLog('debug', `planReviewWire: decidePlanReview dispatch #${dispatchNo} task=${taskId} decision=${word} mode=${permissionModeAfter ?? '-'}`);
|
|
774
880
|
try {
|
|
775
|
-
const wireDecision =
|
|
776
|
-
const wireOpts =
|
|
777
|
-
await decidePlanReviewInner(taskId, wireDecision, permissionModeAfter, dispatchNo, releaseLatch, settle, wireOpts, card,
|
|
881
|
+
const wireDecision = word === 'ask_again' ? 'reject' : word;
|
|
882
|
+
const wireOpts = word === 'ask_again' ? { ...opts, reason: askAgainReasonOf(card, opts) } : opts;
|
|
883
|
+
await decidePlanReviewInner(taskId, wireDecision, permissionModeAfter, dispatchNo, releaseLatch, settle, wireOpts, card, word, cardId);
|
|
778
884
|
}
|
|
779
885
|
finally {
|
|
780
886
|
releaseLatch();
|
|
@@ -797,14 +903,17 @@ function planReviewClientsFor(resolved) {
|
|
|
797
903
|
}
|
|
798
904
|
async function decidePlanReviewInner(taskId, decision, permissionModeAfter, dispatchNo, releaseLatch, settle, opts, card, semanticDecision, cardId) {
|
|
799
905
|
let outcome;
|
|
800
|
-
let
|
|
906
|
+
let cause;
|
|
907
|
+
let unsentProven = false;
|
|
908
|
+
let acceptance;
|
|
909
|
+
let wayOut;
|
|
801
910
|
const gateBefore = card?.checkpointId;
|
|
802
911
|
const approvalWithheld = decision !== 'approve' ? undefined : card === undefined ? 'card_missing' : card.body.kind !== 'shown' || card.checkpointId === undefined ? 'card_withholds' : undefined;
|
|
803
912
|
let effect = 'unconfirmed';
|
|
804
|
-
let stillParkedCause;
|
|
805
913
|
let statusSeen;
|
|
806
914
|
const wire = opts?.wire;
|
|
807
915
|
const resolved = engineWireFor(wire, DEFAULT_SESSION_KEY, usablePlanReviewWireClient);
|
|
916
|
+
const wireGate = wireGateOf(decision, gateBefore);
|
|
808
917
|
const badMode = permissionModeAfter !== undefined && !isPlanReviewModeAfter(permissionModeAfter);
|
|
809
918
|
if (badMode) {
|
|
810
919
|
hostLog('error', `planReviewWire: ${decision} NOT sent — permissionModeAfter must be "default" or "acceptEdits"`);
|
|
@@ -814,7 +923,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
814
923
|
else if (approvalWithheld !== undefined) {
|
|
815
924
|
hostLog('debug', `planReviewWire: not-sent decision=${decision} reason=${approvalWithheld}`);
|
|
816
925
|
effect = 'not_sent';
|
|
817
|
-
|
|
926
|
+
cause = approvalWithheld;
|
|
818
927
|
outcome = renderMessage(msg('approval.planReview.decision.notSent', { decision }), DATA_FACE_LANGUAGE);
|
|
819
928
|
}
|
|
820
929
|
else if (resolved.kind === 'no-target') {
|
|
@@ -836,7 +945,6 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
836
945
|
}
|
|
837
946
|
else {
|
|
838
947
|
const client = clients.decide;
|
|
839
|
-
const checkpointId = gateBefore !== undefined && !engineRefusedGateIds.has(gateBefore) ? gateBefore : undefined;
|
|
840
948
|
const repullRow = async () => {
|
|
841
949
|
const probe = clients.repull();
|
|
842
950
|
if (!probe)
|
|
@@ -854,87 +962,97 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
854
962
|
try {
|
|
855
963
|
const mode = isPlanReviewModeAfter(permissionModeAfter) ? permissionModeAfter : undefined;
|
|
856
964
|
const reason = denyReasonForWire(opts?.reason, `plan-review ${taskId}`);
|
|
857
|
-
const
|
|
858
|
-
if (
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
if (checkpointId !== undefined)
|
|
863
|
-
reqBody.checkpointId = checkpointId;
|
|
864
|
-
let body;
|
|
865
|
-
try {
|
|
866
|
-
body = await sendOnce(undefined, () => client.assistant.planReview(taskId, reqBody));
|
|
965
|
+
const wireBody = planReviewWireBodyOf(decision, reason, mode, wireGate);
|
|
966
|
+
if (wireBody === undefined) {
|
|
967
|
+
effect = 'not_sent';
|
|
968
|
+
cause = 'card_withholds';
|
|
969
|
+
outcome = renderMessage(msg('approval.planReview.decision.notSent', { decision }), DATA_FACE_LANGUAGE);
|
|
867
970
|
}
|
|
868
|
-
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
body = await sendOnce(undefined, () => client.assistant.planReview(taskId, { decision, ...(reason !== undefined ? { reason } : {}), ...(checkpointId !== undefined ? { checkpointId } : {}) }));
|
|
971
|
+
else {
|
|
972
|
+
const firstSent = { ...wireBody };
|
|
973
|
+
let body;
|
|
974
|
+
try {
|
|
975
|
+
body = await sendOnce(undefined, () => client.assistant.planReview(taskId, firstSent));
|
|
874
976
|
}
|
|
875
|
-
|
|
876
|
-
|
|
977
|
+
catch (e1) {
|
|
978
|
+
const conflict = e1;
|
|
979
|
+
const defaultConflict = mode === 'default' && conflict?.status === 400 && conflict?.errorCode === REQUEST_FIELD_CONFLICT;
|
|
980
|
+
if (defaultConflict && !directFailureVerdictOf(e1, directSafetyStopOf(e1)).yields) {
|
|
981
|
+
hostLog('debug', 'planReviewWire: permissionModeAfter "default" refused as not applicable — re-sending the approval without the key (same meaning)');
|
|
982
|
+
const { permissionModeAfter: _modeDropped, ...resend } = wireBody;
|
|
983
|
+
body = await sendOnce(undefined, () => client.assistant.planReview(taskId, resend));
|
|
984
|
+
}
|
|
985
|
+
else {
|
|
986
|
+
throw e1;
|
|
987
|
+
}
|
|
877
988
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
989
|
+
finally {
|
|
990
|
+
releaseLatch();
|
|
991
|
+
}
|
|
992
|
+
const accepted = planReviewAcceptanceOf(body);
|
|
993
|
+
acceptance = accepted;
|
|
994
|
+
hostLog('debug', `planReviewWire: ${decision} → ok (dispatch #${dispatchNo}) ${JSON.stringify(accepted).slice(0, 200)}`);
|
|
995
|
+
const onAccepted = opts?.onAccepted;
|
|
996
|
+
if (onAccepted !== undefined) {
|
|
997
|
+
const payload = planReviewAcceptedPayloadOf(taskId, dispatchNo, semanticDecision, cardId, accepted, wireGate, card);
|
|
998
|
+
invokeHostCallbackIsolated(`planReviewWire: onAccepted task=${taskId}`, () => onAccepted(payload));
|
|
999
|
+
}
|
|
1000
|
+
let postStatus;
|
|
1001
|
+
let gateAfter;
|
|
1002
|
+
let gateKindAfter;
|
|
1003
|
+
try {
|
|
1004
|
+
const post = await repullRow();
|
|
1005
|
+
if (post !== undefined) {
|
|
1006
|
+
postStatus = bodyWordOf(post, 'status');
|
|
1007
|
+
gateAfter = pausedCheckpointIdOf(post?.result);
|
|
1008
|
+
gateKindAfter = pausedGateKindOf(post?.result);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
catch (e) {
|
|
1012
|
+
hostLog('debug', `planReviewWire: post-decide status re-pull failed (fail-soft): ${String(e)}`);
|
|
1013
|
+
}
|
|
1014
|
+
const bodyStatus = accepted.engine.status;
|
|
1015
|
+
const effective = postStatus ?? bodyStatus;
|
|
1016
|
+
statusSeen = effective;
|
|
1017
|
+
effect = planReviewSettledVerdict({ decision, postStatus, bodyStatus, reparked: accepted.reparked, gateBefore, gateAfter, gateKindAfter });
|
|
1018
|
+
const code = accepted.errorCode;
|
|
1019
|
+
if (effect === 'still_parked' && postStatus === 'needs_review' && !accepted.reparked) {
|
|
1020
|
+
outcome =
|
|
1021
|
+
`The plan_review ${decision} returned HTTP 200 but the session is STILL locked on the same review gate ` +
|
|
1022
|
+
`(post-decide status: needs_review). The decision did NOT take effect (known engine issue, RB-471 family). ` +
|
|
1023
|
+
`Tell the user plainly that the ${decision} did not go through; the reliable exits today are approving the plan or cancelling the task.`;
|
|
1024
|
+
}
|
|
1025
|
+
else if (effect === 'still_parked') {
|
|
1026
|
+
outcome =
|
|
1027
|
+
`The plan_review ${decision} was NOT applied: the engine could not continue the task and put the plan back to wait for a decision` +
|
|
1028
|
+
`${code !== undefined ? ` (engine code: ${code})` : ''}. Tell the user plainly that the ${decision} did not go through.`;
|
|
1029
|
+
}
|
|
1030
|
+
else if (effect === 'advanced') {
|
|
1031
|
+
outcome = planReviewAdvancedOutcome(decision, effective === 'needs_review' ? 'needs_review' : 'suspended');
|
|
1032
|
+
}
|
|
1033
|
+
else if (effect === 'took_effect') {
|
|
1034
|
+
outcome =
|
|
1035
|
+
`The plan was ${decision === 'approve' ? 'approved and the parked task resumed to completion' : 'rejected (plan discarded)'} — ${effective === 'completed' ? 'final status' : 'status'}: ${effective} ` +
|
|
1036
|
+
`(${postStatus !== undefined ? 'post-decide re-checked: task left the review gate' : "from the decision's own reply; the post-decide re-check was unavailable"}).`;
|
|
1037
|
+
}
|
|
1038
|
+
else if (effective === 'needs_review' && (gateBefore === undefined || gateAfter === undefined || postStatus !== 'needs_review')) {
|
|
1039
|
+
outcome =
|
|
1040
|
+
`The plan_review ${decision} returned HTTP 200 and the task is waiting at a plan review gate again (post-decide status: needs_review), ` +
|
|
1041
|
+
`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).`;
|
|
1042
|
+
}
|
|
1043
|
+
else if (effective === 'suspended' || effective === 'needs_review') {
|
|
1044
|
+
outcome =
|
|
1045
|
+
`The plan_review ${decision} was accepted by the engine (HTTP 200) and the task is paused again (post-decide status: ${effective}), ` +
|
|
1046
|
+
`${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.`;
|
|
1047
|
+
}
|
|
1048
|
+
else if (effective !== undefined) {
|
|
1049
|
+
outcome =
|
|
1050
|
+
`The plan_review ${decision} was accepted by the engine (HTTP 200), but the task then reported status ${effective}` +
|
|
1051
|
+
`${code !== undefined ? ` (engine code: ${code})` : ''} — not a completion; what the ${decision} led to could not be confirmed.`;
|
|
1052
|
+
}
|
|
1053
|
+
else {
|
|
1054
|
+
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
1055
|
}
|
|
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
1056
|
}
|
|
939
1057
|
}
|
|
940
1058
|
catch (e) {
|
|
@@ -959,40 +1077,34 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
959
1077
|
'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.';
|
|
960
1078
|
}
|
|
961
1079
|
else if (status === 409 && refusedCode === APPROVAL_STALE) {
|
|
962
|
-
let
|
|
963
|
-
if (
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
(
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
const row = await repullRow();
|
|
977
|
-
untrusted = row !== undefined && pausedCheckpointIdOf(row?.result) === checkpointId;
|
|
978
|
-
}
|
|
979
|
-
catch {
|
|
980
|
-
untrusted = false;
|
|
981
|
-
}
|
|
1080
|
+
let verdict = 'gate_moved';
|
|
1081
|
+
if (wireGate !== undefined && planReviewCurrentGateReadingOf(readThrownExtra(e)) === 'absent') {
|
|
1082
|
+
try {
|
|
1083
|
+
const row = await repullRow();
|
|
1084
|
+
const rowGate = pausedCheckpointIdOf(row?.result);
|
|
1085
|
+
verdict =
|
|
1086
|
+
row !== undefined && rowGate === wireGate
|
|
1087
|
+
? 'gate_untrusted'
|
|
1088
|
+
: row !== undefined && rowGate === undefined && bodyWordOf(row, 'status') === 'needs_review'
|
|
1089
|
+
? 'unreadable'
|
|
1090
|
+
: 'gate_moved';
|
|
1091
|
+
}
|
|
1092
|
+
catch {
|
|
1093
|
+
verdict = 'gate_moved';
|
|
982
1094
|
}
|
|
983
1095
|
}
|
|
984
|
-
if (
|
|
985
|
-
noteEngineRefusedGate(
|
|
986
|
-
hostLog('debug', `planReviewWire: approval-stale decision=${decision} reason=${
|
|
1096
|
+
if (verdict === 'gate_untrusted' && wireGate !== undefined)
|
|
1097
|
+
noteEngineRefusedGate(wireGate);
|
|
1098
|
+
hostLog('debug', `planReviewWire: approval-stale decision=${decision} reason=${verdict}`);
|
|
987
1099
|
effect = 'not_applied';
|
|
988
|
-
|
|
1100
|
+
cause = verdict === 'unreadable' ? undefined : verdict;
|
|
989
1101
|
outcome = renderMessage(msg('approval.planReview.decision.refused', { decision }), DATA_FACE_LANGUAGE);
|
|
990
1102
|
}
|
|
991
1103
|
else if (checkpointUnsupportedVersionFromError(e) !== null) {
|
|
992
1104
|
const cu = checkpointUnsupportedVersionFromError(e);
|
|
993
1105
|
hostLog('debug', `planReviewWire: ${decision} → 409 checkpoint.unsupported_version (the server cannot continue this parked review in its stored form; still pending)`);
|
|
994
1106
|
effect = 'still_parked';
|
|
995
|
-
|
|
1107
|
+
cause = 'stored_form_unsupported';
|
|
996
1108
|
outcome = `This ${decision} was not applied. ${checkpointUnsupportedVersionContent(cu, { parkKind: 'plan_review', runId: taskId })}`;
|
|
997
1109
|
}
|
|
998
1110
|
else if (resumeContextUnavailableFromError(e) !== null) {
|
|
@@ -1011,7 +1123,11 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
1011
1123
|
const failureTexts = wireFailureTextsOf(e);
|
|
1012
1124
|
hostLog('debug', `planReviewWire: ${decision} → ${status} ${failureTexts.log.slice(0, 200)}`);
|
|
1013
1125
|
effect = refusalDeliveryOf(e, { route: 'plan_review' }).delivery === 'refused' ? 'not_applied' : 'unconfirmed';
|
|
1014
|
-
|
|
1126
|
+
const engineWayOut = status === 409 ? readDecisionRefusalReading(e, { route: 'plan_review' }).wayOut : undefined;
|
|
1127
|
+
const wayOutClause = engineWayOut === undefined ? undefined : decisionRefusalWayOutContent(engineWayOut);
|
|
1128
|
+
if (wayOutClause !== undefined)
|
|
1129
|
+
wayOut = engineWayOut;
|
|
1130
|
+
outcome = `${`The plan_review decision failed: HTTP ${status} ${failureTexts.reason}`.trim()}${decideFailureNote(e)}${wayOutClause === undefined ? '' : ` — ${wayOutClause}.`}`;
|
|
1015
1131
|
}
|
|
1016
1132
|
else if (noAnswer === 'time-limit' || noAnswer === 'deadline') {
|
|
1017
1133
|
hostLog('debug', `planReviewWire: ${decision} sent but no answer within the request time limit (task ${taskId})`);
|
|
@@ -1024,18 +1140,32 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
1024
1140
|
else {
|
|
1025
1141
|
const failureTexts = wireFailureTextsOf(e);
|
|
1026
1142
|
hostLog('debug', `planReviewWire: ${decision} could not reach the engine: ${failureTexts.log}`);
|
|
1143
|
+
unsentProven = true;
|
|
1027
1144
|
outcome = `The plan_review decision could not reach the engine: ${failureTexts.reason}`;
|
|
1028
1145
|
}
|
|
1029
1146
|
}
|
|
1030
1147
|
}
|
|
1031
1148
|
}
|
|
1032
|
-
|
|
1033
|
-
const
|
|
1149
|
+
const outcomeCause = planReviewOutcomeCauseOf(effect, cause);
|
|
1150
|
+
const sentWithoutGate = gateBefore !== undefined && wireGate === undefined && effect !== 'not_sent' && !unsentProven;
|
|
1151
|
+
outcome = `${outcome}${planReviewDecisionReasonSegment(outcomeCause, card)}${sentWithoutGate ? planReviewSentWithoutGateSegment() : ''}`;
|
|
1152
|
+
const tail = planReviewOutcomeTail(taskId, decision, effect, outcomeCause);
|
|
1034
1153
|
const prompt = `<plan-review-outcome>\n${outcome}\n${tail}\n</plan-review-outcome>`;
|
|
1035
1154
|
const onOutcome = opts?.onOutcome;
|
|
1155
|
+
const meta = {
|
|
1156
|
+
taskId,
|
|
1157
|
+
dispatchNo,
|
|
1158
|
+
decision: semanticDecision,
|
|
1159
|
+
effect,
|
|
1160
|
+
...(outcomeCause !== undefined ? { cause: outcomeCause } : {}),
|
|
1161
|
+
...(cardId !== undefined ? { questionId: cardId } : {}),
|
|
1162
|
+
delivery: planReviewDeliveryOf(acceptance, semanticDecision),
|
|
1163
|
+
...(sentWithoutGate ? { gateIdOmitted: true } : {}),
|
|
1164
|
+
...(wayOut !== undefined ? { wayOut } : {}),
|
|
1165
|
+
};
|
|
1036
1166
|
settle(effect === 'not_sent' || effect === 'still_parked');
|
|
1037
1167
|
try {
|
|
1038
|
-
const delivered = enqueuePlanReviewOutcome(prompt,
|
|
1168
|
+
const delivered = enqueuePlanReviewOutcome(prompt, meta);
|
|
1039
1169
|
if (!delivered) {
|
|
1040
1170
|
if (onOutcome !== undefined) {
|
|
1041
1171
|
hostLog('debug', `planReviewWire: outcome queue port not installed for task ${taskId} — outcome handed to the host's onOutcome callback only`);
|
|
@@ -1050,12 +1180,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
1050
1180
|
}
|
|
1051
1181
|
if (onOutcome !== undefined) {
|
|
1052
1182
|
invokeHostCallbackIsolated(`planReviewWire: onOutcome callback (task ${taskId})`, () => onOutcome({
|
|
1053
|
-
|
|
1054
|
-
dispatchNo,
|
|
1055
|
-
decision: semanticDecision,
|
|
1056
|
-
effect,
|
|
1057
|
-
...(effect === 'still_parked' && stillParkedCause !== undefined ? { cause: stillParkedCause } : {}),
|
|
1058
|
-
...(cardId !== undefined ? { questionId: cardId } : {}),
|
|
1183
|
+
...meta,
|
|
1059
1184
|
detail: outcome,
|
|
1060
1185
|
tail,
|
|
1061
1186
|
prompt,
|
|
@@ -1137,6 +1262,9 @@ function mintPlanReviewReopenCard(taskId, opts, sessionKey, receiptMs, taken) {
|
|
|
1137
1262
|
hostLog('debug', `planReviewWire: reopen-superseded task=${taskId}`);
|
|
1138
1263
|
return Promise.resolve(refusedReopen('not_presented'));
|
|
1139
1264
|
}
|
|
1265
|
+
const early = reopenPresenceRefusal(taskId, opts, 'before');
|
|
1266
|
+
if (early !== undefined)
|
|
1267
|
+
return Promise.resolve(early);
|
|
1140
1268
|
const facts = taken.facts;
|
|
1141
1269
|
const markBefore = dismissalEntryFor(sessionKey, taskId);
|
|
1142
1270
|
const settleOnReceipt = (receipt, firstSight) => receipt.then((presented) => {
|
|
@@ -1158,8 +1286,11 @@ function mintPlanReviewReopenCard(taskId, opts, sessionKey, receiptMs, taken) {
|
|
|
1158
1286
|
samePlanReviewCardFacts(armed.facts, facts)) {
|
|
1159
1287
|
hostLog('debug', `planReviewWire: reopen for task ${taskId} reusing the canonical responder (path=reopen-canonical)`);
|
|
1160
1288
|
retireActiveReopen(sessionKey, canonicalId);
|
|
1289
|
+
const lateReuse = reopenPresenceRefusal(taskId, opts, 'after-retire');
|
|
1290
|
+
if (lateReuse !== undefined)
|
|
1291
|
+
return Promise.resolve(lateReuse);
|
|
1161
1292
|
const receipt = waitForGateArmedFor(sessionKey, [armed.questionId], receiptMs);
|
|
1162
|
-
|
|
1293
|
+
deliverPlanReviewCard(sessionKey, planReviewQuestionFrame(armed.questionId, ARM_QUESTION, armed.shape, armed.facts, sessionKey), armed.facts);
|
|
1163
1294
|
return settleOnReceipt(receipt, firstSight);
|
|
1164
1295
|
}
|
|
1165
1296
|
retireActiveReopen(sessionKey, canonicalId);
|
|
@@ -1168,11 +1299,12 @@ function mintPlanReviewReopenCard(taskId, opts, sessionKey, receiptMs, taken) {
|
|
|
1168
1299
|
hostLog('debug', `planReviewWire: reopen-retire-armed task=${taskId} card=${armedId}`);
|
|
1169
1300
|
}
|
|
1170
1301
|
publishQuestionFrameFor(sessionKey, { type: 'question_complete', questionId: armedId });
|
|
1302
|
+
const lateFresh = reopenPresenceRefusal(taskId, opts, 'after-retire');
|
|
1303
|
+
if (lateFresh !== undefined)
|
|
1304
|
+
return Promise.resolve(lateFresh);
|
|
1171
1305
|
const questionId = freshPlanReviewQuestionId(taskId, reopenIdSuffix());
|
|
1172
1306
|
hostLog('debug', `planReviewWire: path=reopen-fresh task=${taskId} card=${questionId}`);
|
|
1173
|
-
const delivery = opts?.wire
|
|
1174
|
-
? { ...(opts.wire !== undefined ? { wire: opts.wire } : {}), ...(opts.onOutcome !== undefined ? { onOutcome: opts.onOutcome } : {}) }
|
|
1175
|
-
: undefined;
|
|
1307
|
+
const delivery = planReviewDeliveryFrom(opts?.wire, opts?.onOutcome, opts?.onAccepted);
|
|
1176
1308
|
const deliver = opts?.deliverDecision ??
|
|
1177
1309
|
((tid, decision, reason, card) => {
|
|
1178
1310
|
hostLog('debug', `planReviewWire: reopen responder delivering ${decision} for task ${tid} (path=reopen-default)`);
|
|
@@ -1220,12 +1352,32 @@ function mintPlanReviewReopenCard(taskId, opts, sessionKey, receiptMs, taken) {
|
|
|
1220
1352
|
unregister = registerLocalQuestionResponder(questionId, responder, onWithdraw);
|
|
1221
1353
|
activeReopenResponders.set(activeKey, { questionId, unregister: () => unregister(), shape });
|
|
1222
1354
|
const receipt = waitForGateArmedFor(sessionKey, [questionId], receiptMs);
|
|
1223
|
-
|
|
1355
|
+
deliverPlanReviewCard(sessionKey, planReviewQuestionFrame(questionId, REOPEN_QUESTION, shape, facts, sessionKey), facts);
|
|
1224
1356
|
return settleOnReceipt(receipt, firstSight);
|
|
1225
1357
|
}
|
|
1226
1358
|
function refusedReopen(cause) {
|
|
1227
1359
|
return { reopened: false, _sema_reopenRefusal: cause };
|
|
1228
1360
|
}
|
|
1361
|
+
function publishPresenceOf(opts) {
|
|
1362
|
+
return hostPresenceOf(opts, (ask) => {
|
|
1363
|
+
const got = { answered: false };
|
|
1364
|
+
invokeHostCallbackIsolated('planReviewWire: stillHere', () => {
|
|
1365
|
+
got.answer = ask();
|
|
1366
|
+
got.answered = true;
|
|
1367
|
+
return got.answer;
|
|
1368
|
+
});
|
|
1369
|
+
if (!got.answered)
|
|
1370
|
+
throw new Error('stillHere did not answer');
|
|
1371
|
+
return got.answer;
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
function reopenPresenceRefusal(taskId, opts, stage) {
|
|
1375
|
+
const presence = publishPresenceOf(opts);
|
|
1376
|
+
if (presence === 'here')
|
|
1377
|
+
return undefined;
|
|
1378
|
+
hostLog('debug', `planReviewWire: reopen-withheld-${presence} task=${taskId} at=${stage}`);
|
|
1379
|
+
return refusedReopen(presence === 'left' ? 'conversation_left' : 'conversation_unconfirmed');
|
|
1380
|
+
}
|
|
1229
1381
|
function noPresentationSurfaceVerdict(taskId, sessionKey) {
|
|
1230
1382
|
const verdict = { reopened: false, _sema_noPresentationSurface: true };
|
|
1231
1383
|
hostLog('debug', `planReviewWire: reopen for task ${taskId} refused — nothing is subscribed to question frames for session ` +
|
|
@@ -1333,7 +1485,8 @@ export function reopenPlanReviewCard(taskId, opts) {
|
|
|
1333
1485
|
return Promise.resolve(refusedReopen('invalid_call'));
|
|
1334
1486
|
}
|
|
1335
1487
|
const injected = opts;
|
|
1336
|
-
if (opts?.mintFreshQuestionId === false &&
|
|
1488
|
+
if (opts?.mintFreshQuestionId === false &&
|
|
1489
|
+
(injected?.deliverDecision !== undefined || injected?.wire !== undefined || injected?.onOutcome !== undefined || injected?.onAccepted !== undefined)) {
|
|
1337
1490
|
hostLog('error', `planReviewWire: plan_review reopen for task ${taskId} refused — mintFreshQuestionId:false reuses the armed card's responder and its delivery, ` +
|
|
1338
1491
|
'so deliverDecision / wire / onOutcome cannot be given with it; drop mintFreshQuestionId:false to deliver through the injected path');
|
|
1339
1492
|
return Promise.resolve(refusedReopen('invalid_call'));
|
|
@@ -1464,3 +1617,26 @@ export async function readPlanReviewCardFacts(taskId, opts) {
|
|
|
1464
1617
|
return mintPlanReviewCardFacts(taskId, checkpointId, unknownBody('unreadable'));
|
|
1465
1618
|
}
|
|
1466
1619
|
}
|
|
1620
|
+
function planReviewCurrentGateReadingOf(bag) {
|
|
1621
|
+
if (bag.state !== 'object')
|
|
1622
|
+
return 'malformed';
|
|
1623
|
+
try {
|
|
1624
|
+
const d = Object.getOwnPropertyDescriptor(bag.value, 'currentPending');
|
|
1625
|
+
if (d === undefined)
|
|
1626
|
+
return 'absent';
|
|
1627
|
+
if (!Object.hasOwn(d, 'value'))
|
|
1628
|
+
return 'malformed';
|
|
1629
|
+
const slot = d.value;
|
|
1630
|
+
if (typeof slot !== 'object' || slot === null || Array.isArray(slot))
|
|
1631
|
+
return 'malformed';
|
|
1632
|
+
const checkpointId = readOwnDataNonEmptyString(slot, 'checkpointId');
|
|
1633
|
+
return checkpointId === undefined ? 'malformed' : { checkpointId };
|
|
1634
|
+
}
|
|
1635
|
+
catch {
|
|
1636
|
+
return 'malformed';
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
export function readPlanReviewCurrentGate(e) {
|
|
1640
|
+
const reading = planReviewCurrentGateReadingOf(readThrownExtra(e));
|
|
1641
|
+
return typeof reading === 'object' ? reading.checkpointId : undefined;
|
|
1642
|
+
}
|