gestalt-mobile 0.37.0 → 0.37.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.
|
@@ -149,18 +149,18 @@ export async function composeRelayApp(options) {
|
|
|
149
149
|
reconcile: async (sessionId) => {
|
|
150
150
|
const session = sessions.find(sessionId);
|
|
151
151
|
if (!session)
|
|
152
|
-
return;
|
|
152
|
+
return false;
|
|
153
153
|
// Kimi sessions own no child-process projection; one detached history
|
|
154
154
|
// read supplies the whole reconciliation, mirroring the codex tail.
|
|
155
155
|
if (session.provider === 'kimi') {
|
|
156
156
|
if (!kimiRuntime)
|
|
157
|
-
return;
|
|
157
|
+
return false;
|
|
158
158
|
const history = await kimiRuntime.readHistory(session);
|
|
159
159
|
// Forget may commit while the detached history reader is still in
|
|
160
160
|
// flight. Revalidate ownership before handing its result to the
|
|
161
161
|
// registry, whose publisher intentionally keeps the journal strict.
|
|
162
162
|
if (!sessions.find(sessionId))
|
|
163
|
-
return;
|
|
163
|
+
return false;
|
|
164
164
|
const occurredAt = new Date().toISOString();
|
|
165
165
|
activity.observe({
|
|
166
166
|
sessionId,
|
|
@@ -169,16 +169,22 @@ export async function composeRelayApp(options) {
|
|
|
169
169
|
...(session.threadId ? { threadId: session.threadId } : {}),
|
|
170
170
|
...(history.activeTurnId ? { turnId: history.activeTurnId } : {}),
|
|
171
171
|
});
|
|
172
|
-
return;
|
|
172
|
+
return true;
|
|
173
173
|
}
|
|
174
174
|
if (!runtime)
|
|
175
|
-
return;
|
|
175
|
+
return false;
|
|
176
176
|
const history = await runtime.readHistory(session);
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
177
|
+
const children = await runtime.listDirectChildren(session);
|
|
178
|
+
const childProcesses = new Map();
|
|
179
|
+
await mapWithConcurrency([...children], 4, async (child) => {
|
|
180
|
+
childProcesses.set(child.id, await runtime.inspectChildProcesses(session, child));
|
|
181
|
+
});
|
|
182
|
+
// The writer read is also asynchronous; it cannot publish after the
|
|
183
|
+
// durable owner has gone away either.
|
|
180
184
|
if (!sessions.find(sessionId))
|
|
181
|
-
return;
|
|
185
|
+
return false;
|
|
186
|
+
// Publish only after the entire authoritative read succeeds. A fresh
|
|
187
|
+
// root with stale child topology is not a valid supervision snapshot.
|
|
182
188
|
const occurredAt = new Date().toISOString();
|
|
183
189
|
activity.observe({
|
|
184
190
|
sessionId,
|
|
@@ -187,19 +193,11 @@ export async function composeRelayApp(options) {
|
|
|
187
193
|
...(session.threadId ? { threadId: session.threadId } : {}),
|
|
188
194
|
...(history.activeTurnId ? { turnId: history.activeTurnId } : {}),
|
|
189
195
|
});
|
|
190
|
-
const children = await runtime.listDirectChildren(session);
|
|
191
|
-
const childProcesses = new Map();
|
|
192
|
-
await mapWithConcurrency([...children], 4, async (child) => {
|
|
193
|
-
childProcesses.set(child.id, await runtime.inspectChildProcesses(session, child));
|
|
194
|
-
});
|
|
195
|
-
// The writer read is also asynchronous; it cannot publish after the
|
|
196
|
-
// durable owner has gone away either.
|
|
197
|
-
if (!sessions.find(sessionId))
|
|
198
|
-
return;
|
|
199
196
|
activity.childrenReconciled(sessionId, occurredAt, children.map((child) => ({
|
|
200
197
|
...child,
|
|
201
198
|
processes: childProcesses.get(child.id) ?? [],
|
|
202
199
|
})));
|
|
200
|
+
return true;
|
|
203
201
|
},
|
|
204
202
|
});
|
|
205
203
|
const autopilot = new AutopilotCoordinator({
|
|
@@ -157,7 +157,9 @@ export class AgentActivityRegistry {
|
|
|
157
157
|
const now = this.options.now?.() ?? new Date().toISOString();
|
|
158
158
|
this.reconciling(sessionId, now);
|
|
159
159
|
try {
|
|
160
|
-
await this.options.reconcile?.(sessionId);
|
|
160
|
+
const observed = await this.options.reconcile?.(sessionId);
|
|
161
|
+
if (observed !== true)
|
|
162
|
+
throw new Error('AGENT_ACTIVITY_RECONCILE_NO_EVIDENCE');
|
|
161
163
|
if (this.#generation.get(sessionId) === generation)
|
|
162
164
|
this.reconciled(sessionId, this.options.now?.() ?? now);
|
|
163
165
|
}
|
|
@@ -91,7 +91,7 @@ export function decideAutopilot(input) {
|
|
|
91
91
|
return { kind: 'safetyPause', reason: 'actionRateExceeded' };
|
|
92
92
|
if (!activity || activity.confidence !== 'fresh')
|
|
93
93
|
return { kind: 'reconcile' };
|
|
94
|
-
if (Date.parse(now) - Date.parse(activity.root.
|
|
94
|
+
if (Date.parse(now) - Date.parse(activity.root.observedAt) > policy.staleAfterMs)
|
|
95
95
|
return { kind: 'reconcile' };
|
|
96
96
|
const disposition = classifyAgentActivity(activity);
|
|
97
97
|
if (disposition === 'attention')
|
|
@@ -417,9 +417,11 @@ export class AutopilotCoordinator {
|
|
|
417
417
|
case 'scheduleContinuation':
|
|
418
418
|
if (session?.activeTurnId || this.deps.pendingInteraction(sessionId))
|
|
419
419
|
break;
|
|
420
|
+
const plan = this.deps.plan(sessionId)?.plan;
|
|
420
421
|
if (this.deps.executorController &&
|
|
421
|
-
|
|
422
|
-
this.
|
|
422
|
+
plan &&
|
|
423
|
+
!this.requiresRootBoundary(prior, plan) &&
|
|
424
|
+
this.currentExecutor(sessionId, plan, prior.consecutiveNoProgress, prior.executor)) {
|
|
423
425
|
this.enqueue(sessionId, async () => {
|
|
424
426
|
await this.enforceSupervisedLifecycle(sessionId, 'stateChanged');
|
|
425
427
|
});
|
|
@@ -1012,6 +1014,7 @@ export class AutopilotCoordinator {
|
|
|
1012
1014
|
if (this.deps.executorController &&
|
|
1013
1015
|
retained &&
|
|
1014
1016
|
!current.executor?.replacement &&
|
|
1017
|
+
!this.requiresRootBoundary(current, retained.plan) &&
|
|
1015
1018
|
this.currentExecutor(sessionId, retained.plan, current.consecutiveNoProgress, current.executor)) {
|
|
1016
1019
|
const now = this.deps.now();
|
|
1017
1020
|
const cancelled = this.cancelScheduledControl(current, now);
|
|
@@ -1159,6 +1162,10 @@ export class AutopilotCoordinator {
|
|
|
1159
1162
|
!state?.requestedEnabled ||
|
|
1160
1163
|
this.deps.pendingInteraction(sessionId))
|
|
1161
1164
|
return false;
|
|
1165
|
+
// Checkpoints are root-owned report boundaries. Once an executor has made
|
|
1166
|
+
// one eligible, never mechanically resume it past the unreported result.
|
|
1167
|
+
if (this.requiresRootBoundary(state, retained.plan))
|
|
1168
|
+
return false;
|
|
1162
1169
|
const executor = this.currentExecutor(sessionId, retained.plan, state.consecutiveNoProgress, state.executor);
|
|
1163
1170
|
const executorChanged = executor ? this.persistExecutor(sessionId, executor) : false;
|
|
1164
1171
|
if (executor && (await this.reconcileSplitBrain(sessionId, executor)))
|
|
@@ -1167,6 +1174,18 @@ export class AutopilotCoordinator {
|
|
|
1167
1174
|
this.supersedeExecutorCommands(sessionId, retained.identity, retained.plan, executor);
|
|
1168
1175
|
if (executorChanged && this.wakeForExecutorChange(sessionId, executor))
|
|
1169
1176
|
return true;
|
|
1177
|
+
if (executor && this.executorTurnActive(executor, this.deps.activity(sessionId))) {
|
|
1178
|
+
// A malformed root yield must not send a duplicate continuation into a
|
|
1179
|
+
// still-running executor. A valid parked lease already owns its wake;
|
|
1180
|
+
// otherwise retain one bounded reconciliation watchdog as the fallback.
|
|
1181
|
+
const parkedLease = state.supervision?.waitLease;
|
|
1182
|
+
const parkedWakeLive = state.supervision?.outcome === 'parked' &&
|
|
1183
|
+
parkedLease &&
|
|
1184
|
+
this.parkedSubscriptions.get(sessionId) === parkedLease.id;
|
|
1185
|
+
if (!parkedWakeLive)
|
|
1186
|
+
this.armExecutorRefresh(sessionId, this.deps.policy.executorContinuationMaxMs);
|
|
1187
|
+
return true;
|
|
1188
|
+
}
|
|
1170
1189
|
const decision = decideSupervisedLifecycle({
|
|
1171
1190
|
plan: retained.plan,
|
|
1172
1191
|
event,
|
|
@@ -1249,6 +1268,21 @@ export class AutopilotCoordinator {
|
|
|
1249
1268
|
}
|
|
1250
1269
|
}
|
|
1251
1270
|
}
|
|
1271
|
+
requiresRootBoundary(state, plan) {
|
|
1272
|
+
const reportedL2 = new Set(state.checkpoints?.reportedL2Ids ?? []);
|
|
1273
|
+
const reportedL1 = new Set(state.checkpoints?.reportedL1Ids ?? []);
|
|
1274
|
+
const epochs = new Map(state.checkpoints?.completionEpochs?.map((entry) => [entry.target, entry]) ?? []);
|
|
1275
|
+
const epochNeedsReport = (target) => {
|
|
1276
|
+
const epoch = epochs.get(target);
|
|
1277
|
+
return Boolean(epoch?.reopened || epoch?.completed === false);
|
|
1278
|
+
};
|
|
1279
|
+
return plan.steps.some((l1) => l1.children.some((l2) => l2.state === 'DONE' &&
|
|
1280
|
+
(!reportedL2.has(JSON.stringify([l1.id, l2.id])) ||
|
|
1281
|
+
epochNeedsReport(checkpointTarget('l2', l1.id, l2.id)))) ||
|
|
1282
|
+
(l1.state === 'DONE' &&
|
|
1283
|
+
l1.reviewStatus === 'REVIEWED' &&
|
|
1284
|
+
(!reportedL1.has(l1.id) || epochNeedsReport(checkpointTarget('l1', l1.id)))));
|
|
1285
|
+
}
|
|
1252
1286
|
currentExecutor(sessionId, plan, continuationCount, persisted) {
|
|
1253
1287
|
const stepIndex = plan.steps.findIndex((step) => step.id === plan.currentStepId || step.state === 'WIP');
|
|
1254
1288
|
const index = stepIndex >= 0 ? stepIndex : plan.steps.findIndex((step) => step.state !== 'DONE');
|
|
@@ -1987,6 +2021,14 @@ export class AutopilotCoordinator {
|
|
|
1987
2021
|
child.state === 'awaitingAgent' ||
|
|
1988
2022
|
child.ownedProcesses?.some((process) => process.state === 'running' || process.state === 'detached-active')));
|
|
1989
2023
|
}
|
|
2024
|
+
executorTurnActive(executor, activity) {
|
|
2025
|
+
if (activity?.confidence !== 'fresh')
|
|
2026
|
+
return false;
|
|
2027
|
+
const child = activity.subagents.find((candidate) => (candidate.threadId ?? candidate.id) === executor.threadId &&
|
|
2028
|
+
candidate.taskPath === executor.taskPath &&
|
|
2029
|
+
(candidate.continuationGeneration ?? 1) === executor.continuationGeneration);
|
|
2030
|
+
return child?.state === 'working' || child?.state === 'awaitingAgent';
|
|
2031
|
+
}
|
|
1990
2032
|
/** Builds both GET and pre-commit event payloads from the same prospective facts. */
|
|
1991
2033
|
snapshotFor(next, prospectiveControl) {
|
|
1992
2034
|
const session = this.deps.session(next.sessionId);
|
|
@@ -2180,7 +2222,7 @@ export class AutopilotCoordinator {
|
|
|
2180
2222
|
// yield to. A later activity/watchdog event will evaluate it again.
|
|
2181
2223
|
const activity = this.deps.activity(sessionId);
|
|
2182
2224
|
if (activity?.confidence === 'fresh' &&
|
|
2183
|
-
Date.parse(this.deps.now()) - Date.parse(activity.root.
|
|
2225
|
+
Date.parse(this.deps.now()) - Date.parse(activity.root.observedAt) <=
|
|
2184
2226
|
this.deps.policy.staleAfterMs) {
|
|
2185
2227
|
this.evaluate(sessionId);
|
|
2186
2228
|
}
|