gestalt-mobile 0.41.2 → 0.42.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/README.md +13 -0
- package/dist/client/assets/{index-9uZwieND.js → index-BRly-q56.js} +23 -23
- package/dist/client/index.html +1 -1
- package/dist/server/server/cli.js +47 -0
- package/dist/server/server/composition.contracts.js +27 -15
- package/dist/server/server/composition.js +62 -5
- package/dist/server/server/features/agent-activity/model.js +8 -0
- package/dist/server/server/features/autopilot/application/policy.js +4 -4
- package/dist/server/server/features/autopilot/application/service.js +149 -22
- package/dist/server/server/features/autopilot/domain/supervised-lifecycle.js +15 -4
- package/dist/server/server/features/control-plane-trace/export-trace.js +90 -0
- package/dist/server/server/features/org-plan-attention/resolve/endpoint.js +5 -3
- package/dist/server/server/features/skills/list-available/endpoint.js +2 -1
- package/dist/server/server/features/skills/model/skill-profile.js +8 -4
- package/dist/server/server/platform/catalog/profile-command.js +2 -12
- package/dist/server/server/platform/codex/activity-facts.js +2 -0
- package/dist/server/server/platform/codex/codex-process-launcher.js +1 -1
- package/dist/server/server/platform/codex/session-runtime.js +16 -8
- package/dist/server/shared/contracts/org-plan-attention.js +35 -6
- package/dist/server/shared/contracts/org-plan-checkpoint.js +2 -69
- package/package.json +1 -1
|
@@ -321,9 +321,9 @@ export class AutopilotCoordinator {
|
|
|
321
321
|
consecutiveNoProgress: 0,
|
|
322
322
|
nextEvaluationAt: null,
|
|
323
323
|
stopReason: null,
|
|
324
|
-
executor: undefined,
|
|
324
|
+
executor: !replacing && prior.state === 'attentionRequired' ? prior.executor : undefined,
|
|
325
325
|
blocking: undefined,
|
|
326
|
-
supervision: recoverSafetyPause(consumeObsoleteWait(replacing
|
|
326
|
+
supervision: recoverSafetyPause(consumeObsoleteWait(replacing || prior.supervision?.outcome === 'attentionRequired'
|
|
327
327
|
? startSupervisionProtocol(this.progressKey(sessionId))
|
|
328
328
|
: (prior.supervision ?? startSupervisionProtocol(this.progressKey(sessionId))), this.progressKey(sessionId)), this.progressKey(sessionId)),
|
|
329
329
|
updatedAt: now,
|
|
@@ -332,6 +332,64 @@ export class AutopilotCoordinator {
|
|
|
332
332
|
this.evaluate(sessionId);
|
|
333
333
|
return this.snapshot(sessionId);
|
|
334
334
|
}
|
|
335
|
+
/** Converts one typed human permission into one exact durable physical replacement. */
|
|
336
|
+
authorizeExecutorReplacement(sessionId, canonicalTaskName) {
|
|
337
|
+
const state = this.deps.store.find(sessionId);
|
|
338
|
+
const retained = this.deps.plan(sessionId);
|
|
339
|
+
if (!state ||
|
|
340
|
+
state.state !== 'attentionRequired' ||
|
|
341
|
+
state.blocking?.reason !== 'permissionRequired')
|
|
342
|
+
return { accepted: false, reason: 'attentionRequired' };
|
|
343
|
+
if (!retained ||
|
|
344
|
+
state.planIdentity !== retained.identity ||
|
|
345
|
+
state.planFingerprint !== fingerprint(retained.plan))
|
|
346
|
+
return { accepted: false, reason: 'planMismatch' };
|
|
347
|
+
const executor = state.executor;
|
|
348
|
+
if (!executor || executor.canonicalTaskName !== canonicalTaskName || executor.l1State !== 'WIP')
|
|
349
|
+
return { accepted: false, reason: 'executorNotAssigned' };
|
|
350
|
+
if (executor.replacement && this.validReplacement(sessionId, executor.replacement))
|
|
351
|
+
return { accepted: true, identity: executor.replacement };
|
|
352
|
+
const activity = this.deps.activity(sessionId);
|
|
353
|
+
if (activity?.confidence !== 'fresh' || this.executorTurnActive(executor, activity))
|
|
354
|
+
return { accepted: false, reason: 'executorStillActive' };
|
|
355
|
+
const previous = executor.assignment ?? executorAssignment(executor);
|
|
356
|
+
const identity = executorIdentity(canonicalTaskName, previous.generation + 1);
|
|
357
|
+
const now = this.deps.now();
|
|
358
|
+
this.persist({
|
|
359
|
+
...state,
|
|
360
|
+
executor: {
|
|
361
|
+
...executor,
|
|
362
|
+
outcome: 'failed',
|
|
363
|
+
assignment: { ...previous, state: 'unavailable' },
|
|
364
|
+
replacement: {
|
|
365
|
+
...identity,
|
|
366
|
+
planIdentity: retained.identity,
|
|
367
|
+
planFingerprint: fingerprint(retained.plan),
|
|
368
|
+
previous: { ...previous, state: 'unavailable' },
|
|
369
|
+
reason: 'humanPermissionGranted',
|
|
370
|
+
evidence: 'typed-attention-approval',
|
|
371
|
+
},
|
|
372
|
+
commands: executor.commands?.map((command) => ['scheduled', 'issued'].includes(command.status)
|
|
373
|
+
? { ...command, status: 'superseded', updatedAt: now }
|
|
374
|
+
: command),
|
|
375
|
+
},
|
|
376
|
+
updatedAt: now,
|
|
377
|
+
}, undefined, [
|
|
378
|
+
{
|
|
379
|
+
sessionId,
|
|
380
|
+
type: 'autopilot.executor-replacement-authorized',
|
|
381
|
+
payload: {
|
|
382
|
+
canonicalTaskName,
|
|
383
|
+
previousTaskName: previous.taskName,
|
|
384
|
+
taskName: identity.taskName,
|
|
385
|
+
generation: identity.generation,
|
|
386
|
+
evidence: 'typed-attention-approval',
|
|
387
|
+
},
|
|
388
|
+
occurredAt: now,
|
|
389
|
+
},
|
|
390
|
+
]);
|
|
391
|
+
return { accepted: true, identity };
|
|
392
|
+
}
|
|
335
393
|
disable(sessionId) {
|
|
336
394
|
const now = this.deps.now();
|
|
337
395
|
const prior = this.deps.store.find(sessionId) ?? disabledAutopilot(sessionId, now);
|
|
@@ -421,6 +479,7 @@ export class AutopilotCoordinator {
|
|
|
421
479
|
if (this.deps.executorController &&
|
|
422
480
|
plan &&
|
|
423
481
|
!this.requiresRootBoundary(prior, plan) &&
|
|
482
|
+
!this.executorDiagnosisRequired(prior) &&
|
|
424
483
|
this.currentExecutor(sessionId, plan, prior.consecutiveNoProgress, prior.executor)) {
|
|
425
484
|
this.enqueue(sessionId, async () => {
|
|
426
485
|
await this.enforceSupervisedLifecycle(sessionId, 'stateChanged');
|
|
@@ -466,7 +525,12 @@ export class AutopilotCoordinator {
|
|
|
466
525
|
{
|
|
467
526
|
sessionId,
|
|
468
527
|
type: 'autopilot.continuation-scheduled',
|
|
469
|
-
payload: {
|
|
528
|
+
payload: {
|
|
529
|
+
controlId: scheduledControlId,
|
|
530
|
+
...(prior.checkpoints?.activeHandoffId
|
|
531
|
+
? { traceId: prior.checkpoints.activeHandoffId }
|
|
532
|
+
: {}),
|
|
533
|
+
},
|
|
470
534
|
occurredAt: now,
|
|
471
535
|
},
|
|
472
536
|
]);
|
|
@@ -580,7 +644,12 @@ export class AutopilotCoordinator {
|
|
|
580
644
|
: pendingKind === 'terminalReviewAccepted'
|
|
581
645
|
? 'org-plan.terminal-review-reported'
|
|
582
646
|
: 'org-plan.milestone-reported',
|
|
583
|
-
payload: {
|
|
647
|
+
payload: {
|
|
648
|
+
turnId: state.checkpoints.pendingTurnId,
|
|
649
|
+
...(state.checkpoints.activeHandoffId
|
|
650
|
+
? { traceId: state.checkpoints.activeHandoffId }
|
|
651
|
+
: {}),
|
|
652
|
+
},
|
|
584
653
|
occurredAt,
|
|
585
654
|
},
|
|
586
655
|
]);
|
|
@@ -641,6 +710,7 @@ export class AutopilotCoordinator {
|
|
|
641
710
|
...(previous?.completionEpochs ?? []).filter((entry) => entry.target !== target),
|
|
642
711
|
{ target, epoch, reopened: false, completed: true },
|
|
643
712
|
];
|
|
713
|
+
const handoffId = `handoff-${key.slice(0, 24)}`;
|
|
644
714
|
const checkpoints = {
|
|
645
715
|
protocolVersion: 1,
|
|
646
716
|
planIdentity: retained.identity,
|
|
@@ -651,6 +721,7 @@ export class AutopilotCoordinator {
|
|
|
651
721
|
: reportedL1Ids,
|
|
652
722
|
acceptedKeys: boundedUnique([...acceptedKeys, key], 768),
|
|
653
723
|
pendingTurnId: turnId,
|
|
724
|
+
activeHandoffId: handoffId,
|
|
654
725
|
pendingKind: checkpoint.kind,
|
|
655
726
|
checkpointHandoffFailed: false,
|
|
656
727
|
terminalReviewAccepted: checkpoint.kind === 'terminalReviewAccepted' || previous?.terminalReviewAccepted === true,
|
|
@@ -687,10 +758,16 @@ export class AutopilotCoordinator {
|
|
|
687
758
|
l2Id: checkpoint.l2Id,
|
|
688
759
|
position: checkpoint.position,
|
|
689
760
|
turnId,
|
|
761
|
+
traceId: handoffId,
|
|
690
762
|
}
|
|
691
763
|
: checkpoint.kind === 'l1Accepted'
|
|
692
|
-
? {
|
|
693
|
-
|
|
764
|
+
? {
|
|
765
|
+
l1Id: checkpoint.l1Id,
|
|
766
|
+
position: checkpoint.position,
|
|
767
|
+
turnId,
|
|
768
|
+
traceId: handoffId,
|
|
769
|
+
}
|
|
770
|
+
: { turnId, traceId: handoffId },
|
|
694
771
|
occurredAt,
|
|
695
772
|
},
|
|
696
773
|
]);
|
|
@@ -885,15 +962,27 @@ export class AutopilotCoordinator {
|
|
|
885
962
|
const cancelled = this.cancelScheduledControl(prior, now);
|
|
886
963
|
const subagentsWorking = activity.aggregateSubagents === 'working' ||
|
|
887
964
|
activity.aggregateSubagents === 'awaitingAgent';
|
|
965
|
+
const supervision = subagentsWorking &&
|
|
966
|
+
Boolean(this.deps.session(sessionId)?.activeTurnId) &&
|
|
967
|
+
prior.supervision?.outcome === 'probeRequired'
|
|
968
|
+
? reportProbe(prior.supervision, {
|
|
969
|
+
id: `executor-actionable:${createHash('sha256')
|
|
970
|
+
.update(this.progressKey(sessionId))
|
|
971
|
+
.digest('hex')}`,
|
|
972
|
+
kind: 'actionable',
|
|
973
|
+
})
|
|
974
|
+
: prior.supervision;
|
|
888
975
|
if (cancelled ||
|
|
889
976
|
prior.state !== 'monitoring' ||
|
|
890
977
|
prior.nextEvaluationAt ||
|
|
891
|
-
(subagentsWorking && prior.consecutiveNoProgress > 0)
|
|
978
|
+
(subagentsWorking && prior.consecutiveNoProgress > 0) ||
|
|
979
|
+
supervision !== prior.supervision)
|
|
892
980
|
this.persist({
|
|
893
981
|
...prior,
|
|
894
982
|
state: 'monitoring',
|
|
895
983
|
...(cancelled ? { generation: prior.generation + 1, lastControlId: null } : {}),
|
|
896
984
|
...(subagentsWorking ? { consecutiveNoProgress: 0 } : {}),
|
|
985
|
+
...(supervision ? { supervision } : {}),
|
|
897
986
|
nextEvaluationAt: null,
|
|
898
987
|
updatedAt: now,
|
|
899
988
|
}, cancelled);
|
|
@@ -959,7 +1048,17 @@ export class AutopilotCoordinator {
|
|
|
959
1048
|
const issuedControl = { ...priorControl, status: 'issued', updatedAt: now };
|
|
960
1049
|
const events = [
|
|
961
1050
|
...this.snapshotEvents(next, issuedControl),
|
|
962
|
-
{
|
|
1051
|
+
{
|
|
1052
|
+
sessionId,
|
|
1053
|
+
type: 'autopilot.control-issued',
|
|
1054
|
+
payload: {
|
|
1055
|
+
controlId,
|
|
1056
|
+
...(prior.checkpoints?.activeHandoffId
|
|
1057
|
+
? { traceId: prior.checkpoints.activeHandoffId }
|
|
1058
|
+
: {}),
|
|
1059
|
+
},
|
|
1060
|
+
occurredAt: now,
|
|
1061
|
+
},
|
|
963
1062
|
];
|
|
964
1063
|
const control = this.deps.store.claimControlIssued
|
|
965
1064
|
? this.deps.store.claimControlIssued(sessionId, controlId, now, next, events)
|
|
@@ -1033,6 +1132,7 @@ export class AutopilotCoordinator {
|
|
|
1033
1132
|
retained &&
|
|
1034
1133
|
!current.executor?.replacement &&
|
|
1035
1134
|
!this.requiresRootBoundary(current, retained.plan) &&
|
|
1135
|
+
!this.executorDiagnosisRequired(current) &&
|
|
1036
1136
|
this.currentExecutor(sessionId, retained.plan, current.consecutiveNoProgress, current.executor)) {
|
|
1037
1137
|
const now = this.deps.now();
|
|
1038
1138
|
const cancelled = this.cancelScheduledControl(current, now);
|
|
@@ -1047,7 +1147,12 @@ export class AutopilotCoordinator {
|
|
|
1047
1147
|
await this.enforceSupervisedLifecycle(sessionId, 'stateChanged');
|
|
1048
1148
|
return;
|
|
1049
1149
|
}
|
|
1050
|
-
|
|
1150
|
+
// Direct executor continuations account for their own accepted attempts.
|
|
1151
|
+
// Once that bounded loop requests diagnosis, preserve the probe state for
|
|
1152
|
+
// the root instead of counting the root control as another blind retry.
|
|
1153
|
+
const protocol = this.executorDiagnosisRequired(current)
|
|
1154
|
+
? current.supervision
|
|
1155
|
+
: recordAutomaticContinuation(current.supervision ?? startSupervisionProtocol(this.progressKey(sessionId)), this.progressKey(sessionId));
|
|
1051
1156
|
if (protocol.outcome === 'probeRequired') {
|
|
1052
1157
|
this.persist({ ...current, supervision: protocol, updatedAt: this.deps.now() }, undefined, [
|
|
1053
1158
|
{
|
|
@@ -1224,6 +1329,8 @@ export class AutopilotCoordinator {
|
|
|
1224
1329
|
case 'reinspect':
|
|
1225
1330
|
return false;
|
|
1226
1331
|
case 'resumeExecutor':
|
|
1332
|
+
if (this.executorDiagnosisRequired(this.deps.store.find(sessionId) ?? state))
|
|
1333
|
+
return false;
|
|
1227
1334
|
this.armExecutorContinuation(sessionId, decision.action.delayMs, decision.action.threadId, decision.action.generation, { kind: 'partial' });
|
|
1228
1335
|
return true;
|
|
1229
1336
|
case 'monitorProcess': {
|
|
@@ -1258,7 +1365,7 @@ export class AutopilotCoordinator {
|
|
|
1258
1365
|
resultArtifact: processAction.resultArtifact,
|
|
1259
1366
|
});
|
|
1260
1367
|
if (consumed && executor)
|
|
1261
|
-
this.armExecutorContinuation(sessionId, this.deps.policy.executorContinuationBaseMs, executor.threadId, executor.continuationGeneration + 1, {
|
|
1368
|
+
this.armExecutorContinuation(sessionId, this.deps.policy.executorContinuationBaseMs, executor.threadId, executor.continuationGeneration + executor.continuationCount + 1, {
|
|
1262
1369
|
kind: 'processExited',
|
|
1263
1370
|
processId,
|
|
1264
1371
|
resultArtifact: processAction.resultArtifact,
|
|
@@ -1279,7 +1386,7 @@ export class AutopilotCoordinator {
|
|
|
1279
1386
|
reason: 'resourceBudget',
|
|
1280
1387
|
});
|
|
1281
1388
|
if (terminated && executor)
|
|
1282
|
-
this.armExecutorContinuation(sessionId, this.deps.policy.executorContinuationBaseMs, executor.threadId, executor.continuationGeneration + 1, { kind: 'processResourceLimit', processId });
|
|
1389
|
+
this.armExecutorContinuation(sessionId, this.deps.policy.executorContinuationBaseMs, executor.threadId, executor.continuationGeneration + executor.continuationCount + 1, { kind: 'processResourceLimit', processId });
|
|
1283
1390
|
else
|
|
1284
1391
|
this.armExecutorRefresh(sessionId, this.deps.policy.processPollMs);
|
|
1285
1392
|
return true;
|
|
@@ -1366,7 +1473,9 @@ export class AutopilotCoordinator {
|
|
|
1366
1473
|
ownedProcesses: refreshPersistedProcesses(persisted?.canonicalPosition === canonicalPosition ? persisted.ownedProcesses : [], child.ownedProcesses ?? [], this.deps.now()),
|
|
1367
1474
|
outcome: outcome.outcome,
|
|
1368
1475
|
...(outcome.blocking ? { blocking: outcome.blocking } : {}),
|
|
1369
|
-
|
|
1476
|
+
// The roster value and assignment identify the physical l<a>_gN slot.
|
|
1477
|
+
// Logical follow-up attempts are tracked separately by continuationCount.
|
|
1478
|
+
continuationGeneration: replacementChild || !assigned ? (child.continuationGeneration ?? 1) : assigned.generation,
|
|
1370
1479
|
continuationCount: persisted?.canonicalPosition === canonicalPosition
|
|
1371
1480
|
? persisted.continuationCount
|
|
1372
1481
|
: continuationCount,
|
|
@@ -1892,23 +2001,24 @@ export class AutopilotCoordinator {
|
|
|
1892
2001
|
const armedChild = this.deps
|
|
1893
2002
|
.activity(sessionId)
|
|
1894
2003
|
?.subagents.find((child) => (child.threadId ?? child.id) === threadId);
|
|
1895
|
-
const
|
|
2004
|
+
const commandExecutor = state?.executor;
|
|
2005
|
+
const fence = state && retained && commandExecutor
|
|
1896
2006
|
? {
|
|
1897
2007
|
sessionGeneration: state.generation,
|
|
1898
2008
|
planIdentity: retained.identity,
|
|
1899
2009
|
planFingerprint: fingerprint(retained.plan),
|
|
1900
2010
|
canonicalPosition: armedChild?.canonicalPosition ?? state.executor?.canonicalPosition ?? null,
|
|
1901
2011
|
executorThreadId: threadId,
|
|
1902
|
-
|
|
2012
|
+
// The roster generation identifies the physical l<a>_gN slot.
|
|
2013
|
+
// A canonical executor can accept many logical follow-up turns, so
|
|
2014
|
+
// never derive this ownership fence from the follow-up sequence.
|
|
2015
|
+
executorGeneration: commandExecutor.assignment?.generation ?? commandExecutor.continuationGeneration,
|
|
1903
2016
|
supervisionOutcome: state.supervision?.outcome ?? null,
|
|
1904
2017
|
checkpoint: JSON.stringify(state.checkpoints ?? null),
|
|
1905
2018
|
boundaryState: state.state,
|
|
1906
2019
|
}
|
|
1907
2020
|
: null;
|
|
1908
|
-
if (!fence || this.deps.session(sessionId)?.activeTurnId)
|
|
1909
|
-
return;
|
|
1910
|
-
const commandExecutor = state?.executor;
|
|
1911
|
-
if (!commandExecutor)
|
|
2021
|
+
if (!commandExecutor || !fence || this.deps.session(sessionId)?.activeTurnId)
|
|
1912
2022
|
return;
|
|
1913
2023
|
const requested = this.executorCommand(fence, commandExecutor, generation, trigger);
|
|
1914
2024
|
const command = this.scheduleExecutorCommand(sessionId, requested);
|
|
@@ -1974,17 +2084,19 @@ export class AutopilotCoordinator {
|
|
|
1974
2084
|
trigger: trigger.kind,
|
|
1975
2085
|
});
|
|
1976
2086
|
const latest = this.deps.store.find(sessionId);
|
|
1977
|
-
if (latest?.requestedEnabled)
|
|
2087
|
+
if (latest?.requestedEnabled) {
|
|
2088
|
+
const progressKey = this.progressKey(sessionId);
|
|
2089
|
+
const supervision = recordAutomaticContinuation(latest.supervision ?? startSupervisionProtocol(progressKey), progressKey);
|
|
1978
2090
|
this.persist({
|
|
1979
2091
|
...latest,
|
|
1980
2092
|
state: 'monitoring',
|
|
2093
|
+
supervision,
|
|
1981
2094
|
consecutiveNoProgress: latest.consecutiveNoProgress + 1,
|
|
1982
2095
|
...(latest.executor
|
|
1983
2096
|
? {
|
|
1984
2097
|
executor: {
|
|
1985
2098
|
...latest.executor,
|
|
1986
2099
|
outcome: 'partial',
|
|
1987
|
-
continuationGeneration: generation,
|
|
1988
2100
|
continuationCount: latest.executor.continuationCount + 1,
|
|
1989
2101
|
lastActivityAt: this.deps.now(),
|
|
1990
2102
|
},
|
|
@@ -1993,6 +2105,7 @@ export class AutopilotCoordinator {
|
|
|
1993
2105
|
nextEvaluationAt: null,
|
|
1994
2106
|
updatedAt: this.deps.now(),
|
|
1995
2107
|
});
|
|
2108
|
+
}
|
|
1996
2109
|
}
|
|
1997
2110
|
catch (error) {
|
|
1998
2111
|
// Only an explicit app-server rejection is safe to retry. A lost
|
|
@@ -2055,8 +2168,19 @@ export class AutopilotCoordinator {
|
|
|
2055
2168
|
sessionId,
|
|
2056
2169
|
type: eventType,
|
|
2057
2170
|
payload: eventType === 'autopilot.turn-failed'
|
|
2058
|
-
? {
|
|
2059
|
-
|
|
2171
|
+
? {
|
|
2172
|
+
controlId,
|
|
2173
|
+
code: failureCode,
|
|
2174
|
+
...(this.deps.store.find(sessionId)?.checkpoints?.activeHandoffId
|
|
2175
|
+
? { traceId: this.deps.store.find(sessionId).checkpoints.activeHandoffId }
|
|
2176
|
+
: {}),
|
|
2177
|
+
}
|
|
2178
|
+
: {
|
|
2179
|
+
controlId,
|
|
2180
|
+
...(this.deps.store.find(sessionId)?.checkpoints?.activeHandoffId
|
|
2181
|
+
? { traceId: this.deps.store.find(sessionId).checkpoints.activeHandoffId }
|
|
2182
|
+
: {}),
|
|
2183
|
+
},
|
|
2060
2184
|
occurredAt,
|
|
2061
2185
|
},
|
|
2062
2186
|
]
|
|
@@ -2085,6 +2209,9 @@ export class AutopilotCoordinator {
|
|
|
2085
2209
|
child.state === 'awaitingAgent' ||
|
|
2086
2210
|
child.ownedProcesses?.some((process) => process.state === 'running' || process.state === 'detached-active')));
|
|
2087
2211
|
}
|
|
2212
|
+
executorDiagnosisRequired(state) {
|
|
2213
|
+
return state.supervision?.outcome === 'probeRequired';
|
|
2214
|
+
}
|
|
2088
2215
|
executorTurnActive(executor, activity) {
|
|
2089
2216
|
if (activity?.confidence !== 'fresh')
|
|
2090
2217
|
return false;
|
|
@@ -179,7 +179,8 @@ export function parsePersistedSupervisedLifecycle(value) {
|
|
|
179
179
|
const replacementPrevious = replacementPreviousValue
|
|
180
180
|
? parseExecutorAssignment(replacementPreviousValue)
|
|
181
181
|
: undefined;
|
|
182
|
-
const replacementReason = replacementValue &&
|
|
182
|
+
const replacementReason = replacementValue &&
|
|
183
|
+
stringValue(replacementValue.reason, ['explicitExecutorRejection', 'humanPermissionGranted']);
|
|
183
184
|
const replacementEvidence = replacementValue && boundedText(replacementValue.evidence);
|
|
184
185
|
if (!canonicalPosition ||
|
|
185
186
|
!canonicalTaskName ||
|
|
@@ -200,7 +201,8 @@ export function parsePersistedSupervisedLifecycle(value) {
|
|
|
200
201
|
!replacementPlanIdentity ||
|
|
201
202
|
!replacementPlanFingerprint ||
|
|
202
203
|
(replacementValue.previous !== undefined && !replacementPrevious) ||
|
|
203
|
-
(replacementReason !== undefined &&
|
|
204
|
+
(replacementReason !== undefined &&
|
|
205
|
+
!['explicitExecutorRejection', 'humanPermissionGranted'].includes(replacementReason)) ||
|
|
204
206
|
(replacementValue.evidence !== undefined && !replacementEvidence))))
|
|
205
207
|
return undefined;
|
|
206
208
|
if (replacementValue) {
|
|
@@ -304,7 +306,10 @@ export function parsePersistedSupervisedLifecycle(value) {
|
|
|
304
306
|
ownedProcesses,
|
|
305
307
|
outcome,
|
|
306
308
|
...(executorBlocking ? { blocking: executorBlocking } : {}),
|
|
307
|
-
|
|
309
|
+
// Earlier releases incremented this value after every follow-up, even
|
|
310
|
+
// though the roster derives it from the immutable physical task name. A
|
|
311
|
+
// durable assignment is authoritative during reconciliation.
|
|
312
|
+
continuationGeneration: assignment?.generation ?? continuationGeneration,
|
|
308
313
|
continuationCount,
|
|
309
314
|
...(resumeFailures ? { resumeFailures } : {}),
|
|
310
315
|
...(assignment ? { assignment } : {}),
|
|
@@ -385,6 +390,11 @@ function parseCheckpoints(value) {
|
|
|
385
390
|
const pendingTurnId = value.pendingTurnId === null ? null : boundedText(value.pendingTurnId);
|
|
386
391
|
if (pendingTurnId === undefined)
|
|
387
392
|
return undefined;
|
|
393
|
+
const activeHandoffId = value.activeHandoffId === undefined || value.activeHandoffId === null
|
|
394
|
+
? null
|
|
395
|
+
: boundedText(value.activeHandoffId);
|
|
396
|
+
if (activeHandoffId === undefined)
|
|
397
|
+
return undefined;
|
|
388
398
|
if (value.checkpointHandoffFailed !== undefined &&
|
|
389
399
|
typeof value.checkpointHandoffFailed !== 'boolean')
|
|
390
400
|
return undefined;
|
|
@@ -432,6 +442,7 @@ function parseCheckpoints(value) {
|
|
|
432
442
|
reportedL1Ids,
|
|
433
443
|
acceptedKeys,
|
|
434
444
|
pendingTurnId,
|
|
445
|
+
...(activeHandoffId ? { activeHandoffId } : {}),
|
|
435
446
|
pendingKind: pendingKind,
|
|
436
447
|
...(value.checkpointHandoffFailed === undefined
|
|
437
448
|
? {}
|
|
@@ -537,7 +548,7 @@ export function decideSupervisedLifecycle(input) {
|
|
|
537
548
|
action: {
|
|
538
549
|
kind: 'resumeExecutor',
|
|
539
550
|
threadId: input.executor.threadId,
|
|
540
|
-
generation: input.executor.continuationGeneration + 1,
|
|
551
|
+
generation: input.executor.continuationGeneration + input.executor.continuationCount + 1,
|
|
541
552
|
delayMs,
|
|
542
553
|
},
|
|
543
554
|
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2026 Dyne.org foundation
|
|
3
|
+
* Designed by Denis Roio <jaromil@dyne.org>
|
|
4
|
+
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
*/
|
|
6
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
7
|
+
const relevant = /^(?:org-plan\.|autopilot\.|agent\.activity\.|session\.status\.)/;
|
|
8
|
+
function objectPayload(value) {
|
|
9
|
+
try {
|
|
10
|
+
const parsed = JSON.parse(value);
|
|
11
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
12
|
+
? parsed
|
|
13
|
+
: {};
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function correlation(payload) {
|
|
20
|
+
for (const key of ['traceId', 'handoffId', 'controlId', 'turnId', 'requestId'])
|
|
21
|
+
if (typeof payload[key] === 'string' && payload[key])
|
|
22
|
+
return String(payload[key]);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
function diagnose(events) {
|
|
26
|
+
const diagnoses = [];
|
|
27
|
+
if (!events.length)
|
|
28
|
+
return ['no control-plane events found for this session'];
|
|
29
|
+
const checkpointIndex = events.findLastIndex((event) => event.type.endsWith('-checkpointed'));
|
|
30
|
+
const currentHandoff = checkpointIndex < 0 ? events : events.slice(checkpointIndex);
|
|
31
|
+
const types = new Set(currentHandoff.map((event) => event.type));
|
|
32
|
+
if ([...types].some((type) => type.endsWith('-checkpointed')) &&
|
|
33
|
+
!types.has('autopilot.continuation-scheduled'))
|
|
34
|
+
diagnoses.push('checkpoint persisted, but no continuation was scheduled');
|
|
35
|
+
if (types.has('autopilot.continuation-scheduled') &&
|
|
36
|
+
!types.has('autopilot.control-issued') &&
|
|
37
|
+
!types.has('autopilot.executor-resumed'))
|
|
38
|
+
diagnoses.push('continuation scheduled, but no control or executor dispatch was observed');
|
|
39
|
+
if (types.has('autopilot.control-issued') &&
|
|
40
|
+
!types.has('autopilot.turn-started') &&
|
|
41
|
+
!types.has('autopilot.turn-failed'))
|
|
42
|
+
diagnoses.push('control issued, but no turn acceptance or failure was observed');
|
|
43
|
+
if (types.has('org-plan.checkpoint-handoff-failed'))
|
|
44
|
+
diagnoses.push('checkpoint response handoff failed; inspect the matching trace ID');
|
|
45
|
+
if (currentHandoff.some((event) => event.type === 'agent.activity.updated'))
|
|
46
|
+
diagnoses.push('backend agent projection was recorded; browser delivery still requires the client event cursor');
|
|
47
|
+
return diagnoses;
|
|
48
|
+
}
|
|
49
|
+
export function exportControlPlaneTrace(databasePath, sessionId, generatedAt = new Date().toISOString()) {
|
|
50
|
+
const database = new DatabaseSync(databasePath, { readOnly: true });
|
|
51
|
+
try {
|
|
52
|
+
const rows = database
|
|
53
|
+
.prepare('SELECT sequence,occurred_at,type,payload_json FROM session_events WHERE session_id = ? ORDER BY sequence')
|
|
54
|
+
.all(sessionId);
|
|
55
|
+
const events = rows
|
|
56
|
+
.filter((row) => relevant.test(row.type))
|
|
57
|
+
.map((row) => {
|
|
58
|
+
const payload = objectPayload(row.payload_json);
|
|
59
|
+
return {
|
|
60
|
+
sequence: row.sequence,
|
|
61
|
+
occurredAt: row.occurred_at,
|
|
62
|
+
type: row.type,
|
|
63
|
+
payload,
|
|
64
|
+
traceId: correlation(payload),
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
return { schemaVersion: 1, sessionId, generatedAt, events, diagnoses: diagnose(events) };
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
database.close();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export function formatControlPlaneTrace(trace) {
|
|
74
|
+
const lines = [
|
|
75
|
+
`Control-plane trace for ${trace.sessionId}`,
|
|
76
|
+
`Generated: ${trace.generatedAt}`,
|
|
77
|
+
'',
|
|
78
|
+
];
|
|
79
|
+
for (const event of trace.events) {
|
|
80
|
+
const correlationLabel = event.traceId ? ` trace=${event.traceId}` : '';
|
|
81
|
+
lines.push(`${String(event.sequence).padStart(6)} ${event.occurredAt} ${event.type}${correlationLabel}`);
|
|
82
|
+
}
|
|
83
|
+
lines.push('', 'Diagnosis:');
|
|
84
|
+
if (!trace.diagnoses.length)
|
|
85
|
+
lines.push(' No incomplete control-plane transition detected.');
|
|
86
|
+
else
|
|
87
|
+
for (const diagnosis of trace.diagnoses)
|
|
88
|
+
lines.push(` - ${diagnosis}`);
|
|
89
|
+
return `${lines.join('\n')}\n`;
|
|
90
|
+
}
|
|
@@ -34,9 +34,11 @@ export function registerResolveOrgPlanAttention(app, deps) {
|
|
|
34
34
|
? 'ATTENTION_OPERATION_STALE'
|
|
35
35
|
: outcome.kind === 'legacyUnsupported'
|
|
36
36
|
? 'ATTENTION_LEGACY_UNSUPPORTED'
|
|
37
|
-
: outcome.kind === '
|
|
38
|
-
? '
|
|
39
|
-
: '
|
|
37
|
+
: outcome.kind === 'replacementRejected'
|
|
38
|
+
? 'EXECUTOR_REPLACEMENT_NOT_AUTHORIZED'
|
|
39
|
+
: outcome.kind === 'writerCleared'
|
|
40
|
+
? 'ATTENTION_WRITER_CLEARED'
|
|
41
|
+
: 'ATTENTION_WRITER_UNAVAILABLE';
|
|
40
42
|
return reply.code(outcome.kind === 'noActive' ? 404 : 409).send({ code });
|
|
41
43
|
});
|
|
42
44
|
}
|
|
@@ -40,7 +40,8 @@ export function registerListAvailableSkills(app, deps) {
|
|
|
40
40
|
return {
|
|
41
41
|
source: project ? 'project' : 'native',
|
|
42
42
|
errors: discovered.errors.map(({ message }) => ({ message })),
|
|
43
|
-
skills:
|
|
43
|
+
skills: skills
|
|
44
|
+
.filter((skill) => !isAlwaysAdvertisedSkill(skill))
|
|
44
45
|
.sort((left, right) => left.path.localeCompare(right.path))
|
|
45
46
|
.map((skill) => ({
|
|
46
47
|
name: skill.name,
|
|
@@ -56,7 +56,10 @@ export const availableSkillSchema = z
|
|
|
56
56
|
.strict();
|
|
57
57
|
/** Gestalt workflow skills are session infrastructure, not optional profile entries. */
|
|
58
58
|
export function isAlwaysAdvertisedSkill(skill) {
|
|
59
|
-
return skill.name
|
|
59
|
+
return isGestaltSkillName(skill.name);
|
|
60
|
+
}
|
|
61
|
+
export function isGestaltSkillName(name) {
|
|
62
|
+
return name.startsWith('gestalt:');
|
|
60
63
|
}
|
|
61
64
|
const profileDocumentSchema = z
|
|
62
65
|
.object({
|
|
@@ -195,8 +198,6 @@ export function reconcileSkillSelectionSnapshot(discovered, selection) {
|
|
|
195
198
|
*/
|
|
196
199
|
export function compileSkillOverride(input) {
|
|
197
200
|
const effective = selectEffectiveSkillSelection(input);
|
|
198
|
-
if (effective.selection === undefined)
|
|
199
|
-
return { source: 'native', skillsConfig: undefined, warnings: [] };
|
|
200
201
|
const reconciled = reconcileSkillSelectionSnapshot(input.discovered, effective.selection);
|
|
201
202
|
return {
|
|
202
203
|
source: effective.source,
|
|
@@ -211,7 +212,10 @@ export function createSkillProfile(input) {
|
|
|
211
212
|
return {
|
|
212
213
|
version: 1,
|
|
213
214
|
name: normalizeSkillProfileName(input.name),
|
|
214
|
-
skills
|
|
215
|
+
// Gestalt skills are mandatory thread infrastructure. Legacy profiles are
|
|
216
|
+
// normalized on read so they cannot pin a stale plugin-cache version or
|
|
217
|
+
// expose a meaningless enable/disable control in the editor.
|
|
218
|
+
skills: createSkillSelection(input.skills.filter((skill) => !isGestaltSkillName(skill.name))),
|
|
215
219
|
};
|
|
216
220
|
}
|
|
217
221
|
/** Parse the version-1 YAML document without performing filesystem access. */
|
|
@@ -3,16 +3,6 @@
|
|
|
3
3
|
* Designed by Denis Roio <jaromil@dyne.org>
|
|
4
4
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
*/
|
|
6
|
-
export function profileAppServerCommand(
|
|
7
|
-
|
|
8
|
-
return skillsConfig === undefined
|
|
9
|
-
? base
|
|
10
|
-
: {
|
|
11
|
-
...base,
|
|
12
|
-
args: [
|
|
13
|
-
...base.args,
|
|
14
|
-
'--config',
|
|
15
|
-
`skills.config = [${skillsConfig.map((entry) => `{ path = ${JSON.stringify(entry.path)}, enabled = ${entry.enabled} }`).join(', ')}]`,
|
|
16
|
-
],
|
|
17
|
-
};
|
|
6
|
+
export function profileAppServerCommand() {
|
|
7
|
+
return { command: 'codex', args: ['app-server', '--stdio'] };
|
|
18
8
|
}
|
|
@@ -147,10 +147,12 @@ function collaborationAction(tool) {
|
|
|
147
147
|
spawnAgent: 'spawn_agent',
|
|
148
148
|
sendInput: 'send_input',
|
|
149
149
|
resumeAgent: 'resume_agent',
|
|
150
|
+
followupTask: 'resume_agent',
|
|
150
151
|
closeAgent: 'close_agent',
|
|
151
152
|
spawn_agent: 'spawn_agent',
|
|
152
153
|
send_input: 'send_input',
|
|
153
154
|
resume_agent: 'resume_agent',
|
|
155
|
+
followup_task: 'resume_agent',
|
|
154
156
|
close_agent: 'close_agent',
|
|
155
157
|
wait: 'wait',
|
|
156
158
|
}[tool];
|
|
@@ -7,7 +7,7 @@ import { spawn } from 'node:child_process';
|
|
|
7
7
|
import { JsonRpcClient } from './json-rpc-client.js';
|
|
8
8
|
import { profileAppServerCommand } from '../catalog/profile-command.js';
|
|
9
9
|
export function launchCodexAppServer(input) {
|
|
10
|
-
const launch = profileAppServerCommand(
|
|
10
|
+
const launch = profileAppServerCommand();
|
|
11
11
|
const child = spawn(launch.command, launch.args, {
|
|
12
12
|
cwd: input.cwd,
|
|
13
13
|
env: codexChildEnvironment(input.environment),
|