gestalt-mobile 0.35.3 → 0.35.4

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.
@@ -588,7 +588,7 @@ export async function composeRelayApp(options) {
588
588
  if (rawInteraction.kind === 'autopilotWaitLease') {
589
589
  const lease = rawInteraction.payload;
590
590
  const rootOwned = origin.kind === 'root' && origin.physicalTurnId === session.activeTurnId;
591
- const accepted = rootOwned &&
591
+ const registration = rootOwned &&
592
592
  (lease.version === 2
593
593
  ? autopilot.registerProactiveWait(sessionId, {
594
594
  id: lease.reportId,
@@ -602,10 +602,12 @@ export async function composeRelayApp(options) {
602
602
  leaseId: lease.leaseId,
603
603
  wakeConditions: lease.wakeConditions,
604
604
  }));
605
- if (!accepted) {
606
- if (rootOwned)
605
+ if (registration !== true) {
606
+ if (rootOwned && registration === false)
607
607
  autopilot.rejectProbe(sessionId);
608
- return (runtime?.resolveServerRequest(sessionId, rawInteraction.requestId, autopilotWaitLeaseToolResponse(false)) === true);
608
+ return (runtime?.resolveServerRequest(sessionId, rawInteraction.requestId, autopilotWaitLeaseToolResponse(false, registration === 'wakeAlreadySatisfied'
609
+ ? 'wakeAlreadySatisfied'
610
+ : 'automaticContinuationUnavailable')) === true);
609
611
  }
610
612
  return (runtime?.resolveServerRequest(sessionId, rawInteraction.requestId, autopilotWaitLeaseToolResponse()) === true);
611
613
  }
@@ -162,6 +162,24 @@ export class AutopilotCoordinator {
162
162
  report.maxWaitMs > AUTOPILOT_WAIT_MAX_MS)
163
163
  return false;
164
164
  const now = this.deps.now();
165
+ if (this.executorWakeAlreadySatisfied(sessionId, state, report.wakeConditions)) {
166
+ // The executor edge is already visible. Fence every pending automatic
167
+ // callback before returning control to the still-active root; otherwise
168
+ // a scheduled executor resume could race same-turn supervision.
169
+ const cancelled = this.cancelScheduledControl(state, now);
170
+ this.persist({
171
+ ...state,
172
+ generation: state.generation + 1,
173
+ ...(cancelled ? { lastControlId: null } : {}),
174
+ nextEvaluationAt: null,
175
+ updatedAt: now,
176
+ }, cancelled);
177
+ this.timers.get(sessionId)?.();
178
+ this.timers.delete(sessionId);
179
+ this.completionTimers.get(sessionId)?.();
180
+ this.completionTimers.delete(sessionId);
181
+ return 'wakeAlreadySatisfied';
182
+ }
165
183
  const resumeAt = new Date(Date.parse(now) + report.maxWaitMs).toISOString();
166
184
  const protocol = state.supervision ?? startSupervisionProtocol(this.progressKey(sessionId));
167
185
  const nextProtocol = registerProtocolWait(protocol, { ...report, resumeAt });
@@ -185,6 +203,19 @@ export class AutopilotCoordinator {
185
203
  this.armWaitDeadline(sessionId, report.leaseId, resumeAt);
186
204
  return true;
187
205
  }
206
+ /** Prevents an edge-triggered executor lease from parking after its edge. */
207
+ executorWakeAlreadySatisfied(sessionId, state, wakeConditions) {
208
+ if (!wakeConditions.includes('executorChanged'))
209
+ return false;
210
+ if (this.completionTimers.has(sessionId))
211
+ return true;
212
+ const retained = this.deps.plan(sessionId);
213
+ const activity = this.deps.activity(sessionId);
214
+ if (!retained || activity?.confidence !== 'fresh')
215
+ return false;
216
+ const executor = this.currentExecutor(sessionId, retained.plan, state.consecutiveNoProgress, state.executor);
217
+ return Boolean(executor && !this.authoritativeExecutorActive(state, retained.plan, activity));
218
+ }
188
219
  /** Fails closed only for the currently active bounded probe. */
189
220
  rejectProbe(sessionId) {
190
221
  const state = this.deps.store.find(sessionId);
@@ -1074,6 +1105,10 @@ export class AutopilotCoordinator {
1074
1105
  this.completionTimers.delete(sessionId);
1075
1106
  this.executorTimers.get(sessionId)?.();
1076
1107
  this.executorTimers.delete(sessionId);
1108
+ this.cancelScheduledExecutorCommands(sessionId);
1109
+ }
1110
+ /** Cancels executor authority while allowing a delivered stale callback to audit itself. */
1111
+ cancelScheduledExecutorCommands(sessionId) {
1077
1112
  const state = this.deps.store.find(sessionId);
1078
1113
  const executor = state?.executor;
1079
1114
  if (state && executor?.commands?.some((command) => command.status === 'scheduled'))
@@ -74,7 +74,7 @@ export function parseAutopilotWaitLease(value) {
74
74
  maxWaitMs: value.maxWaitMs,
75
75
  };
76
76
  }
77
- export function autopilotWaitLeaseToolResponse(accepted = true) {
77
+ export function autopilotWaitLeaseToolResponse(accepted = true, reason = 'automaticContinuationUnavailable') {
78
78
  return {
79
79
  success: true,
80
80
  contentItems: [
@@ -82,7 +82,7 @@ export function autopilotWaitLeaseToolResponse(accepted = true) {
82
82
  type: 'inputText',
83
83
  text: accepted
84
84
  ? '{"accepted":true}'
85
- : '{"accepted":false,"reason":"automaticContinuationUnavailable","next":"continueSameTurn"}',
85
+ : JSON.stringify({ accepted: false, reason, next: 'continueSameTurn' }),
86
86
  },
87
87
  ],
88
88
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gestalt-mobile",
3
- "version": "0.35.3",
3
+ "version": "0.35.4",
4
4
  "description": "Mobile-first web relay for durable Codex development sessions",
5
5
  "keywords": [
6
6
  "codex",