@sema-agent/client-core 0.85.0 → 0.85.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.
@@ -79,11 +79,105 @@ export function readCancelRequested(body) {
79
79
  const v = body.cancelRequested;
80
80
  return typeof v === 'boolean' ? v : undefined;
81
81
  }
82
+ const REOPEN_VERDICT_KEYS = [
83
+ 'reopened',
84
+ 'firstSight',
85
+ 'presented',
86
+ 'decidedWithoutCard',
87
+ 'pendingRowGone',
88
+ '_sema_decisionInFlight',
89
+ 'dismissedByUser',
90
+ '_sema_noPresentationSurface',
91
+ ];
92
+ function snapshotReopenVerdict(raw) {
93
+ if (raw === null || raw === undefined)
94
+ return { reopened: false };
95
+ if (typeof raw !== 'object' && typeof raw !== 'function')
96
+ return raw;
97
+ const src = raw;
98
+ let nullProto = false;
99
+ try {
100
+ nullProto = Object.getPrototypeOf(raw) === null;
101
+ }
102
+ catch {
103
+ nullProto = false;
104
+ }
105
+ const snap = nullProto ? Object.create(null) : {};
106
+ const put = (k, v) => {
107
+ Object.defineProperty(snap, k, { value: v, enumerable: true, writable: true, configurable: true });
108
+ };
109
+ let own = [];
110
+ try {
111
+ own = Reflect.ownKeys(raw);
112
+ }
113
+ catch {
114
+ own = [];
115
+ }
116
+ const copied = new Set();
117
+ for (const k of own) {
118
+ let enumerable = false;
119
+ try {
120
+ enumerable = Object.prototype.propertyIsEnumerable.call(raw, k);
121
+ }
122
+ catch {
123
+ enumerable = false;
124
+ }
125
+ if (!enumerable)
126
+ continue;
127
+ let v;
128
+ let readable = true;
129
+ try {
130
+ v = src[k];
131
+ }
132
+ catch {
133
+ readable = false;
134
+ }
135
+ if (!readable)
136
+ continue;
137
+ put(k, v);
138
+ copied.add(k);
139
+ }
140
+ for (const k of REOPEN_VERDICT_KEYS) {
141
+ if (copied.has(k))
142
+ continue;
143
+ let v;
144
+ let readable = true;
145
+ try {
146
+ v = src[k];
147
+ }
148
+ catch {
149
+ readable = false;
150
+ }
151
+ if (readable && v !== undefined)
152
+ put(k, v);
153
+ }
154
+ return snap;
155
+ }
82
156
  function reopenRefusedForDecisionInFlight(verdict) {
83
- return verdict !== null && verdict.reopened === false && verdict._sema_decisionInFlight === true;
157
+ try {
158
+ return verdict !== null && verdict.reopened === false && verdict._sema_decisionInFlight === true;
159
+ }
160
+ catch {
161
+ return false;
162
+ }
84
163
  }
85
164
  function reopenRefusedForUserDismissal(verdict) {
86
- return verdict !== null && verdict.reopened === false && verdict.dismissedByUser === true;
165
+ try {
166
+ return verdict !== null && verdict.reopened === false && verdict.dismissedByUser === true;
167
+ }
168
+ catch {
169
+ return false;
170
+ }
171
+ }
172
+ function reopenRefusedForNoPresentationSurface(verdict) {
173
+ try {
174
+ return (verdict !== null &&
175
+ verdict.reopened === false &&
176
+ verdict._sema_noPresentationSurface === true);
177
+ }
178
+ catch {
179
+ return false;
180
+ }
87
181
  }
88
182
  function planReviewReopenTriggerFor(origin) {
89
183
  if (origin === 'user')
@@ -93,7 +187,31 @@ function planReviewReopenTriggerFor(origin) {
93
187
  return undefined;
94
188
  }
95
189
  function reopenDelivered(verdict) {
96
- return verdict.reopened === true && verdict.presented !== false;
190
+ let reopened;
191
+ let presented;
192
+ try {
193
+ reopened = verdict.reopened;
194
+ }
195
+ catch {
196
+ return false;
197
+ }
198
+ if (reopened !== true)
199
+ return false;
200
+ try {
201
+ presented = verdict.presented;
202
+ }
203
+ catch {
204
+ presented = undefined;
205
+ }
206
+ return presented !== false;
207
+ }
208
+ function reopenedOfOutcome(outcome) {
209
+ try {
210
+ return outcome.reopened ?? null;
211
+ }
212
+ catch {
213
+ return null;
214
+ }
97
215
  }
98
216
  export function atMostOnceFailureClass(e) {
99
217
  const status = e?.status;
@@ -299,7 +417,7 @@ async function planVerdict(taskId, deps) {
299
417
  try {
300
418
  const trigger = planReviewReopenTriggerFor(deps?.submissionOrigin);
301
419
  const verdict = trigger === undefined ? deps?.reopenPlanReview?.(taskId) : deps?.reopenPlanReview?.(taskId, { trigger });
302
- return (await verdict) ?? { reopened: false };
420
+ return snapshotReopenVerdict(await verdict);
303
421
  }
304
422
  catch {
305
423
  return { reopened: false };
@@ -307,7 +425,7 @@ async function planVerdict(taskId, deps) {
307
425
  }
308
426
  async function askVerdict(taskId, deps) {
309
427
  try {
310
- return (await deps?.reopenAskPark?.(taskId)) ?? { reopened: false };
428
+ return snapshotReopenVerdict(await deps?.reopenAskPark?.(taskId));
311
429
  }
312
430
  catch {
313
431
  return { reopened: false };
@@ -324,6 +442,7 @@ async function planReviewArm(taskId, signal, deps) {
324
442
  ...(reopenRefusedForDecisionInFlight(verdict) ? { decisionInFlight: true } : {}),
325
443
  ...(verdict.pendingRowGone === true ? { pendingRowGone: true } : {}),
326
444
  ...(reopenRefusedForUserDismissal(verdict) ? { dismissedByUser: true } : {}),
445
+ ...(reopenRefusedForNoPresentationSurface(verdict) ? { noPresentationSurface: true } : {}),
327
446
  };
328
447
  }
329
448
  async function askParkArm(taskId, signal, runs, deps) {
@@ -364,6 +483,7 @@ async function askParkArm(taskId, signal, runs, deps) {
364
483
  decidePath: signal.pendingGate?.decidePath ?? null,
365
484
  ...(inFlight ? { decisionInFlight: true } : {}),
366
485
  ...(verdict.pendingRowGone === true ? { pendingRowGone: true } : {}),
486
+ ...(reopenRefusedForNoPresentationSurface(verdict) ? { noPresentationSurface: true } : {}),
367
487
  };
368
488
  }
369
489
  async function probeRunStatusOnce(taskId, durable, durableGet, deps) {
@@ -655,6 +775,7 @@ export function activeRunSelfHealRow(outcome, signal, copy, origin, follow) {
655
775
  }
656
776
  const INJECTED_LEAD = 'A follow-up message sema sent on its own (not one you typed)';
657
777
  const USER_CLOSED_CARD_NOT_PUT_BACK = 'sema does not put a card you closed back on screen by itself';
778
+ const NO_CARD_VIEW_ATTACHED = 'this session has no view attached that can show decision cards';
658
779
  function injectedSubmissionRow(outcome, following = false) {
659
780
  const lead = INJECTED_LEAD;
660
781
  const id = 'taskId' in outcome && outcome.taskId ? ` (id ${outcome.taskId})` : '';
@@ -677,17 +798,19 @@ function injectedSubmissionRow(outcome, following = false) {
677
798
  }
678
799
  if (outcome.kind === 'running-steered' && outcome.delivery === 'queued') {
679
800
  const queuedOn = `${lead} is queued on an earlier reply${id} that is paused waiting for a decision`;
680
- if (outcome.reopened !== null && reopenDelivered(outcome.reopened)) {
801
+ const reopened = reopenedOfOutcome(outcome);
802
+ if (reopened !== null && reopenDelivered(reopened)) {
681
803
  return `${queuedOn}; sema has shown that decision card, and the message is picked up once you answer it.`;
682
804
  }
683
- if (reopenRefusedForDecisionInFlight(outcome.reopened)) {
805
+ if (reopenRefusedForDecisionInFlight(reopened)) {
684
806
  return `${queuedOn}; your answer to it is still on its way, and the message is picked up once that answer is applied.`;
685
807
  }
686
- if (reopenRefusedForUserDismissal(outcome.reopened)) {
808
+ if (reopenRefusedForUserDismissal(reopened)) {
687
809
  return (`${queuedOn}; you closed that card yourself, so sema did not put it back on screen — send a message to bring it ` +
688
810
  `back, and the message is picked up once that decision is made.`);
689
811
  }
690
- return `${queuedOn} sema could not show here; the message is picked up only once that decision is made.`;
812
+ const noView = reopenRefusedForNoPresentationSurface(reopened) ? ` (${NO_CARD_VIEW_ATTACHED})` : '';
813
+ return `${queuedOn} sema could not show here${noView}; the message is picked up only once that decision is made.`;
691
814
  }
692
815
  if (outcome.kind === 'running-steered' && outcome.delivery === 'parked_for_wake') {
693
816
  return (`${lead} has not reached the model: the earlier reply${id} had already finished, so the message was set aside on it ` +
@@ -710,9 +833,13 @@ function injectedSubmissionRow(outcome, following = false) {
710
833
  `sema is following that reply and will show whatever it asks for next; nothing for you to do now.`
711
834
  : `${lead} was passed to the reply already in progress${id} instead of starting a new one — ` +
712
835
  `watch that reply for what it does with it.`;
713
- case 'not-delivered':
836
+ case 'not-delivered': {
837
+ const noView = (outcome.kind === 'ask-reopen-failed' || outcome.kind === 'plan-review-reopen-failed') && outcome.noPresentationSurface === true
838
+ ? ` (${NO_CARD_VIEW_ATTACHED})`
839
+ : '';
714
840
  return (`${lead} was not delivered: this session was still busy with an earlier reply${id}, and there was no open card ` +
715
- `you could answer to free it. sema did not retry, so the model has not seen it.`);
841
+ `you could answer to free it${noView}. sema did not retry, so the model has not seen it.`);
842
+ }
716
843
  }
717
844
  }
718
845
  function decisionInFlightRow(parkedOn) {
@@ -780,8 +907,9 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false, ke
780
907
  const viaEngine = engineDecide && outcome.decidePath
781
908
  ? ` You can also decide it on the engine directly: POST ${outcome.decidePath}.`
782
909
  : '';
910
+ const noView = outcome.noPresentationSurface === true ? `: ${NO_CARD_VIEW_ATTACHED}` : '';
783
911
  return (`The previous turn is parked waiting for your decision (run ${outcome.taskId}) and sema could ` +
784
- `not reopen that card here. It did NOT cancel the run — that would have decided it for you. ` +
912
+ `not reopen that card here${noView}. It did NOT cancel the run — that would have decided it for you. ` +
785
913
  (keepSessionWayOut !== null
786
914
  ? `Your message was NOT sent. To keep this conversation, ${keepSessionWayOut}: the card comes back when the session resumes, if it is still waiting. Or ${wayOut} if you no longer want it.${viaEngine}`
787
915
  : `Your message was NOT sent; ${wayOut} if you no longer want it.${viaEngine}`));
@@ -797,8 +925,9 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false, ke
797
925
  const viaEngine = engineDecide && outcome.decidePath
798
926
  ? ` You can also decide it on the engine directly: POST ${outcome.decidePath}.`
799
927
  : '';
928
+ const noView = outcome.noPresentationSurface === true ? `: ${NO_CARD_VIEW_ATTACHED}` : '';
800
929
  return (`The previous turn is parked waiting for a plan review (run ${outcome.taskId}) and sema could not ` +
801
- `reopen that approval card. It did NOT cancel the run — that would have discarded the plan for ` +
930
+ `reopen that approval card${noView}. It did NOT cancel the run — that would have discarded the plan for ` +
802
931
  (keepSessionWayOut !== null
803
932
  ? `you. Your message was NOT sent. To keep this conversation, ${keepSessionWayOut}: the card comes back when the session resumes, if it is still waiting. Or ${wayOut} if you no longer want that plan.${viaEngine}`
804
933
  : `you. Your message was NOT sent; ${wayOut} if you no longer want that plan.${viaEngine}`));
@@ -816,15 +945,18 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false, ke
816
945
  }
817
946
  if (outcome.delivery === 'queued') {
818
947
  const parked = outcome.status ? `parked (status ${outcome.status})` : 'parked';
819
- const next = outcome.reopened !== null && reopenDelivered(outcome.reopened)
948
+ const reopened = reopenedOfOutcome(outcome);
949
+ const next = reopened !== null && reopenDelivered(reopened)
820
950
  ? `sema has surfaced that decision card — answering it is what lets that run resume and pick your message up.`
821
- : reopenRefusedForDecisionInFlight(outcome.reopened)
951
+ : reopenRefusedForDecisionInFlight(reopened)
822
952
  ? `Your answer to that decision is still on its way to the engine, so sema did not show the card again; ` +
823
953
  `that run moves on once the engine applies it.`
824
- : reopenRefusedForUserDismissal(outcome.reopened)
954
+ : reopenRefusedForUserDismissal(reopened)
825
955
  ? `You closed that decision card yourself, so sema did not put it back on screen; nothing will resume that ` +
826
956
  `run until that decision is made — send a message to bring the card back.`
827
- : `sema could not surface that decision card here, so nothing will resume that run until that decision is made; ` +
957
+ : `sema could not surface that decision card here` +
958
+ (reopenRefusedForNoPresentationSurface(reopened) ? ` (${NO_CARD_VIEW_ATTACHED})` : '') +
959
+ `, so nothing will resume that run until that decision is made; ` +
828
960
  `${wayOut} if you no longer want it.`;
829
961
  return (`${handle} is not actually working right now: the engine reports it ${parked} on a decision it needs ` +
830
962
  `from you, so it queued your message on that park instead of running it. ${next} ${textOnly} ` +
@@ -9,6 +9,7 @@ export declare function gateArmedWaitMs(): number;
9
9
  export declare function waitForGateArmed(keys: readonly string[], timeoutMs: number): Promise<boolean>;
10
10
  export declare function waitForGateArmedFor(sessionKey: string, keys: readonly string[], timeoutMs: number): Promise<boolean>;
11
11
  export declare function registerArmedGateFromQuestionId(questionId: unknown): void;
12
+ export declare function registerArmedGateFromQuestionIdFor(sessionKey: string, questionId: unknown): void;
12
13
  export declare function planReviewArmedKey(taskId: string): string;
13
14
  export declare function planReviewArmedKeyFor(sessionKey: string, taskId: string): string;
14
15
  export declare function notePlanReviewAnswered(questionId: unknown): void;
@@ -120,7 +120,7 @@ function armedKeyForQuestionIdFor(sessionKey, questionId) {
120
120
  export function registerArmedGateFromQuestionId(questionId) {
121
121
  registerArmedGateFromQuestionIdFor(DEFAULT_SESSION_KEY, questionId);
122
122
  }
123
- function registerArmedGateFromQuestionIdFor(sessionKey, questionId) {
123
+ export function registerArmedGateFromQuestionIdFor(sessionKey, questionId) {
124
124
  if (typeof questionId !== 'string' || questionId.length === 0)
125
125
  return;
126
126
  if (questionId.startsWith(PLAN_REVIEW_QUESTION_ID_PREFIX) && !questionId.includes(REOPEN_ID_TAIL)) {
@@ -827,6 +827,12 @@ function mintPlanReviewReopenCard(taskId, opts, sessionKey, receiptMs) {
827
827
  revokePlanReviewUserDismissal(sessionKey, taskId, markBefore);
828
828
  return { reopened: true, firstSight };
829
829
  }
830
+ function noPresentationSurfaceVerdict(taskId, sessionKey) {
831
+ const verdict = { reopened: false, _sema_noPresentationSurface: true };
832
+ hostLog('debug', `planReviewWire: reopen for task ${taskId} refused — nothing is subscribed to question frames for session ` +
833
+ `${JSON.stringify(sessionKey)} (onQuestionFrameFor), so the card has nowhere to render`);
834
+ return verdict;
835
+ }
830
836
  async function reopenAfterDismissalCheck(taskId, opts, sessionKey, receiptMs, mark, recheckReadStart) {
831
837
  const refusedByUser = { reopened: false, dismissedByUser: true };
832
838
  try {
@@ -863,6 +869,11 @@ async function reopenAfterDismissalCheck(taskId, opts, sessionKey, receiptMs, ma
863
869
  hostLog('debug', `planReviewWire: reopen for task ${taskId} not minted — the dismissal record changed while it was being checked (stale evaluation)`);
864
870
  return await staleDismissalVerdict(current, sessionKey, taskId, receiptMs);
865
871
  }
872
+ if (!hasQuestionOverlayFor(sessionKey)) {
873
+ if (fresh)
874
+ dropDismissalEntry(sessionKey, taskId, mark);
875
+ return noPresentationSurfaceVerdict(taskId, sessionKey);
876
+ }
866
877
  if (!fresh) {
867
878
  hostLog('debug', `planReviewWire: automatic reopen for task ${taskId} refused — the user closed this gate's card (no newer gate instance proven)`);
868
879
  return refusedByUser;
@@ -878,8 +889,7 @@ async function reopenAfterDismissalCheck(taskId, opts, sessionKey, receiptMs, ma
878
889
  putDismissalEntry(sessionKey, taskId, reopening);
879
890
  let verdict = { reopened: false };
880
891
  try {
881
- if (hasQuestionOverlayFor(sessionKey))
882
- verdict = await mintPlanReviewReopen(taskId, opts, sessionKey, receiptMs);
892
+ verdict = await mintPlanReviewReopen(taskId, opts, sessionKey, receiptMs);
883
893
  }
884
894
  finally {
885
895
  const presented = verdict.reopened === true;
@@ -937,7 +947,8 @@ export function reopenPlanReviewCard(taskId, opts) {
937
947
  const recheckReadStart = readStart === undefined ? guardStartedAt : Math.min(readStart, guardStartedAt);
938
948
  const sessionKey = opts?.sessionKey ?? DEFAULT_SESSION_KEY;
939
949
  if (!hasQuestionOverlayFor(sessionKey)) {
940
- return receiptMs === undefined ? { reopened: false } : Promise.resolve({ reopened: false });
950
+ const noSurface = noPresentationSurfaceVerdict(taskId, sessionKey);
951
+ return receiptMs === undefined ? noSurface : Promise.resolve(noSurface);
941
952
  }
942
953
  if (sessionKey !== DEFAULT_SESSION_KEY && opts?.deliverDecision === undefined && opts?.wire === undefined) {
943
954
  hostLog('error', `planReviewWire: plan_review 卡未重开 —— sessionKey=${JSON.stringify(sessionKey)} 是非默认槽,` +
package/dist/host.d.ts CHANGED
@@ -56,7 +56,7 @@ export interface HostPorts {
56
56
  }
57
57
  export declare function installHost(ports: HostPorts): () => void;
58
58
  export declare function installHostFor(sessionKey: string, ports: Omit<HostPorts, 'queue'>): () => void;
59
- export declare function hostLog(level: AdapterLogLevel, msg: string, data?: unknown): void;
59
+ export declare function hostLog(level: AdapterLogLevel, msg: string, data?: unknown): boolean;
60
60
  export declare function hostProbe(channel: string, line: string): void;
61
61
  export declare function hostSettings(): SettingsPort | undefined;
62
62
  export declare function hostSettingsFor(sessionKey: string): SettingsPort | undefined;
package/dist/host.js CHANGED
@@ -46,11 +46,20 @@ export function installHostFor(sessionKey, ports) {
46
46
  slot.state = prev;
47
47
  };
48
48
  }
49
+ function callDiagnosticPort(call) {
50
+ try {
51
+ call();
52
+ return true;
53
+ }
54
+ catch {
55
+ return false;
56
+ }
57
+ }
49
58
  export function hostLog(level, msg, data) {
50
- hostSlot(DEFAULT_SESSION_KEY).state.log?.(level, msg, data);
59
+ return callDiagnosticPort(() => hostSlot(DEFAULT_SESSION_KEY).state.log?.(level, msg, data));
51
60
  }
52
61
  export function hostProbe(channel, line) {
53
- hostSlot(DEFAULT_SESSION_KEY).state.probe?.(channel, line);
62
+ callDiagnosticPort(() => hostSlot(DEFAULT_SESSION_KEY).state.probe?.(channel, line));
54
63
  }
55
64
  export function hostSettings() {
56
65
  return hostSettingsFor(DEFAULT_SESSION_KEY);
package/dist/index.d.ts CHANGED
@@ -169,7 +169,7 @@ export * from './coreValuePorts.js';
169
169
  export * from './hitl/askGateWire.js';
170
170
  export * from './hitl/planReviewWire.js';
171
171
  export { PLAN_REVIEW_QUESTION_ID_PREFIX, REOPEN_ID_TAIL, approvalCallKey, askGateQuestionId, planReviewQuestionId, liveFrameCallKey, armedKeyFromQuestionId, } from './hitl/gateIdentity.js';
172
- export { _resetArmedGateRegistryForTest, _setGateArmedWaitMsForTest, clearArmedGate, gateArmedWaitMs, notePlanReviewAnswered, onGateArmed, planReviewArmedKey, registerArmedGate, registerArmedGateFor, registerArmedGateFromQuestionId, waitForGateArmed, wasGateArmed, wasGateArmedFor, } from './hitl/armedGateRegistry.js';
172
+ export { _resetArmedGateRegistryForTest, _setGateArmedWaitMsForTest, clearArmedGate, gateArmedWaitMs, notePlanReviewAnswered, onGateArmed, planReviewArmedKey, registerArmedGate, registerArmedGateFor, registerArmedGateFromQuestionId, registerArmedGateFromQuestionIdFor, waitForGateArmed, wasGateArmed, wasGateArmedFor, } from './hitl/armedGateRegistry.js';
173
173
  export * from './hitl/parkOwnership.js';
174
174
  export * from './hitl/parkRowBirthWait.js';
175
175
  export * from './hitl/approvalDecisionNoteAudit.js';
package/dist/index.js CHANGED
@@ -155,7 +155,7 @@ export * from './coreValuePorts.js';
155
155
  export * from './hitl/askGateWire.js';
156
156
  export * from './hitl/planReviewWire.js';
157
157
  export { PLAN_REVIEW_QUESTION_ID_PREFIX, REOPEN_ID_TAIL, approvalCallKey, askGateQuestionId, planReviewQuestionId, liveFrameCallKey, armedKeyFromQuestionId, } from './hitl/gateIdentity.js';
158
- export { _resetArmedGateRegistryForTest, _setGateArmedWaitMsForTest, clearArmedGate, gateArmedWaitMs, notePlanReviewAnswered, onGateArmed, planReviewArmedKey, registerArmedGate, registerArmedGateFor, registerArmedGateFromQuestionId, waitForGateArmed, wasGateArmed, wasGateArmedFor, } from './hitl/armedGateRegistry.js';
158
+ export { _resetArmedGateRegistryForTest, _setGateArmedWaitMsForTest, clearArmedGate, gateArmedWaitMs, notePlanReviewAnswered, onGateArmed, planReviewArmedKey, registerArmedGate, registerArmedGateFor, registerArmedGateFromQuestionId, registerArmedGateFromQuestionIdFor, waitForGateArmed, wasGateArmed, wasGateArmedFor, } from './hitl/armedGateRegistry.js';
159
159
  export * from './hitl/parkOwnership.js';
160
160
  export * from './hitl/parkRowBirthWait.js';
161
161
  export * from './hitl/approvalDecisionNoteAudit.js';
@@ -71,6 +71,8 @@ export interface TaskRequestOmission {
71
71
  readonly cause: TaskRequestOmissionCause;
72
72
  }
73
73
  export declare function taskRequestOmissionCauseDetail(cause: unknown): string;
74
+ export declare const TASK_REQUEST_OMISSION_CAUSE_NOTICES: Readonly<Record<TaskRequestOmissionCause, string>>;
75
+ export declare function taskRequestOmissionCauseNotice(cause: unknown): string;
74
76
  export interface TaskRequestAssembly {
75
77
  readonly request: TaskRequestLike;
76
78
  readonly omitted: readonly TaskRequestOmission[];
@@ -75,6 +75,18 @@ export function taskRequestOmissionCauseDetail(cause) {
75
75
  const word = typeof cause === 'string' && cause.length > 0 ? cause : '(none)';
76
76
  return `a cause this build does not know (${word}) — this build does not know this cause: receipt causes come from this build's own frozen vocabulary, so a word outside it was not produced here; check whatever supplied it`;
77
77
  }
78
+ export const TASK_REQUEST_OMISSION_CAUSE_NOTICES = Object.freeze({
79
+ upstream_absent: 'Not sent with this request: this version has no way to send this setting in any request, so giving it or leaving it out makes no difference to what is sent.',
80
+ other_channel: 'Not sent from here: that part of the request belongs to a separate step, so what goes out for it, if anything, is decided there rather than by the value given here.',
81
+ off_lane: 'Not sent with this request: this kind of request does not carry this setting, so giving it or leaving it out makes no difference to what this request sends.',
82
+ not_live: 'Not sent with this request: the request was marked as not going to a live engine (for example an offline or test run), and this setting is only sent on requests that go to one.',
83
+ });
84
+ const TASK_REQUEST_OMISSION_UNKNOWN_CAUSE_NOTICE = 'Not sent with this request, for a reason this version does not recognize.';
85
+ export function taskRequestOmissionCauseNotice(cause) {
86
+ return typeof cause === 'string' && Object.hasOwn(TASK_REQUEST_OMISSION_CAUSE_NOTICES, cause)
87
+ ? TASK_REQUEST_OMISSION_CAUSE_NOTICES[cause]
88
+ : TASK_REQUEST_OMISSION_UNKNOWN_CAUSE_NOTICE;
89
+ }
78
90
  const row = (field) => ({ via: 'matrix', field });
79
91
  const INPUT_KEY_ROUTES = {
80
92
  objective: { via: 'always' },
@@ -11,12 +11,9 @@ export function noteBgOwnerAbsence(leg, taskId) {
11
11
  noted.delete(oldest);
12
12
  }
13
13
  noted.add(key);
14
- try {
15
- hostLog('warn', `[subagent-owner] ${leg}: no host-run mapping for ${taskId} — not connecting (owner ledger absent ⇒ honest absence; active-run fallback removed per [4000] Q3)`);
16
- }
17
- catch {
14
+ const delivered = hostLog('warn', `[subagent-owner] ${leg}: no host-run mapping for ${taskId} — not connecting (owner ledger absent ⇒ honest absence; active-run fallback removed per [4000] Q3)`);
15
+ if (!delivered)
18
16
  noted.delete(key);
19
- }
20
17
  }
21
18
  export function __resetBgOwnerAbsenceForTests() {
22
19
  noted.clear();