gestalt-mobile 0.41.1 → 0.41.3
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/dist/client/assets/{index-G2PznC5F.js → index-BRly-q56.js} +23 -23
- package/dist/client/index.html +1 -1
- package/dist/server/server/composition.contracts.js +192 -50
- package/dist/server/server/composition.js +175 -33
- package/dist/server/server/features/agent-activity/model.js +8 -2
- package/dist/server/server/features/autopilot/application/policy.js +4 -4
- package/dist/server/server/features/autopilot/application/service.js +103 -15
- package/dist/server/server/features/autopilot/domain/supervised-lifecycle.js +9 -4
- package/dist/server/server/features/org-plan-attention/resolve/endpoint.js +5 -3
- package/dist/server/server/features/org-plan-checkpoint/application/validate.js +83 -15
- package/dist/server/server/features/plans/application/supervised-plan-registry.js +8 -1
- 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/codex-process-launcher.js +1 -1
- package/dist/server/server/platform/codex/session-runtime.js +16 -8
- package/dist/server/server/platform/persistence/sqlite-pending-interaction-store.js +12 -6
- package/dist/server/shared/contracts/org-plan-attention.js +43 -7
- package/dist/server/shared/contracts/org-plan-checkpoint.js +5 -1
- package/dist/server/shared/contracts/plan-signal.js +3 -1
- package/package.json +1 -1
|
@@ -7,6 +7,7 @@ import { createHash, randomBytes, randomUUID } from 'node:crypto';
|
|
|
7
7
|
import { existsSync } from 'node:fs';
|
|
8
8
|
import { homedir } from 'node:os';
|
|
9
9
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
10
|
+
import { debuglog } from 'node:util';
|
|
10
11
|
import { buildApp } from './app.js';
|
|
11
12
|
import { FilesystemWorkspaceCatalog } from './platform/catalog/filesystem-workspace-catalog.js';
|
|
12
13
|
import { FilesystemWorkspaceFiles } from './platform/filesystem/filesystem-workspace-files.js';
|
|
@@ -48,7 +49,7 @@ import { toAgentActivityDto } from './features/agent-activity/activity-dto.js';
|
|
|
48
49
|
import { decodeAgentActivityFacts } from './platform/codex/activity-facts.js';
|
|
49
50
|
import { completedCommandId, compactOrgPlanCheckpointKind, isAgentCapacityRecoveryCall, isAutopilotWaitLeaseCall, resolvedServerRequestId, toPendingInteraction, } from './platform/codex/server-request.js';
|
|
50
51
|
import { parseOrgPlanCheckpoint, toOrgPlanCheckpointToolResponse, } from '../shared/contracts/org-plan-checkpoint.js';
|
|
51
|
-
import { resolveOrgPlanCheckpointSignal,
|
|
52
|
+
import { hasActiveL1Writer, resolveOrgPlanCheckpointSignal, validateOrgPlanCheckpoint, } from './features/org-plan-checkpoint/application/validate.js';
|
|
52
53
|
import { isValidInteractionResponse, isValidQuizInteractionResponse, } from './features/sessions/interaction/response-validator.js';
|
|
53
54
|
import { promoteRecentThread } from './features/sessions/promote-recent-thread/use-case.js';
|
|
54
55
|
import { FilesystemSkillProfileStore } from './platform/skills/filesystem-skill-profile-store.js';
|
|
@@ -65,11 +66,13 @@ import { AutopilotCoordinator } from './features/autopilot/application/service.j
|
|
|
65
66
|
import { deriveSessionStatus } from './features/sessions/session-status.js';
|
|
66
67
|
import { AUTOPILOT_CONTINUATION_PROMPT, AUTOPILOT_EXECUTOR_CONTINUATION_PROMPT, autopilotExecutorLaunchPrompt, defaultAutopilotPolicy, } from './features/autopilot/application/policy.js';
|
|
67
68
|
import { createRelyingPartyConfig } from './config.js';
|
|
68
|
-
import { parseOrgPlanAttention } from '../shared/contracts/org-plan-attention.js';
|
|
69
|
+
import { parseOrgPlanAttention, parseOrgPlanAttentionToolResponse, toOrgPlanAttentionAcknowledgement, } from '../shared/contracts/org-plan-attention.js';
|
|
69
70
|
import { autopilotWaitLeaseToolResponse, } from '../shared/contracts/autopilot-wait-lease.js';
|
|
70
71
|
import { agentCapacityRecoveryToolResponse } from '../shared/contracts/agent-capacity-recovery.js';
|
|
71
72
|
import { pwaIconUrl } from './features/pwa/register-routes.js';
|
|
72
73
|
const generatedProtocolVersion = 'codex-cli 0.144.3';
|
|
74
|
+
const checkpointDiagnostic = debuglog('gestalt-mobile:checkpoint');
|
|
75
|
+
const attentionDiagnostic = debuglog('gestalt-mobile:attention');
|
|
73
76
|
export async function composeRelayApp(options) {
|
|
74
77
|
const passkeyAuthEnabled = options.passkeyAuthEnabled ?? true;
|
|
75
78
|
const relyingParty = createRelyingPartyConfig(options.relyingParty.publicOrigin);
|
|
@@ -104,6 +107,7 @@ export async function composeRelayApp(options) {
|
|
|
104
107
|
const autopilotStore = new SqliteAutopilotStore(database);
|
|
105
108
|
const attentionResolutionOperations = new Map();
|
|
106
109
|
const checkpointHandoffTimers = new Map();
|
|
110
|
+
const attentionAcknowledgementTimers = new Map();
|
|
107
111
|
const checkpointHandoffRecoveries = new Set();
|
|
108
112
|
const supervisedPlans = new SupervisedPlanRegistry();
|
|
109
113
|
const planStatusSource = new FilesystemPlanStatusSource(join(dirname(databasePath), 'plans'));
|
|
@@ -396,8 +400,6 @@ export async function composeRelayApp(options) {
|
|
|
396
400
|
explicit: session.effectiveSkillSelection.skills,
|
|
397
401
|
}).skillsConfig;
|
|
398
402
|
const project = await skillProfiles.readWorkspaceDefault(session.workspacePath);
|
|
399
|
-
if (!options.explicitSkillProfile && !project)
|
|
400
|
-
return undefined;
|
|
401
403
|
return compileSkillOverride({
|
|
402
404
|
discovered: catalog.skills,
|
|
403
405
|
explicit: options.explicitSkillProfile?.skills,
|
|
@@ -471,6 +473,9 @@ export async function composeRelayApp(options) {
|
|
|
471
473
|
// request: the client can deliver them as a follow-up prompt instead.
|
|
472
474
|
if (interaction.kind === 'quiz')
|
|
473
475
|
continue;
|
|
476
|
+
if (interaction.kind === 'orgPlanAttention' &&
|
|
477
|
+
interactions.resolutionState(sessionId, interaction.requestId) === 'acknowledged')
|
|
478
|
+
continue;
|
|
474
479
|
if (interactions.resolve(sessionId, interaction.requestId, occurredAt, outcome))
|
|
475
480
|
publishInteractionResolved(sessionId, interaction.requestId, occurredAt, outcome);
|
|
476
481
|
}
|
|
@@ -551,6 +556,36 @@ export async function composeRelayApp(options) {
|
|
|
551
556
|
handle.unref?.();
|
|
552
557
|
checkpointHandoffTimers.set(key, { handle, token });
|
|
553
558
|
};
|
|
559
|
+
const cancelAttentionAcknowledgementTimer = (sessionId, requestId) => {
|
|
560
|
+
const key = checkpointTimerKey(sessionId, requestId);
|
|
561
|
+
const timer = attentionAcknowledgementTimers.get(key);
|
|
562
|
+
if (!timer)
|
|
563
|
+
return;
|
|
564
|
+
clearTimeout(timer.handle);
|
|
565
|
+
attentionAcknowledgementTimers.delete(key);
|
|
566
|
+
};
|
|
567
|
+
const armAttentionAcknowledgementDeadline = (sessionId, requestId) => {
|
|
568
|
+
cancelAttentionAcknowledgementTimer(sessionId, requestId);
|
|
569
|
+
const key = checkpointTimerKey(sessionId, requestId);
|
|
570
|
+
const token = Symbol(key);
|
|
571
|
+
const handle = setTimeout(() => {
|
|
572
|
+
if (attentionAcknowledgementTimers.get(key)?.token !== token)
|
|
573
|
+
return;
|
|
574
|
+
attentionAcknowledgementTimers.delete(key);
|
|
575
|
+
const ownedRuntime = runtime;
|
|
576
|
+
const session = sessions.find(sessionId);
|
|
577
|
+
if (!ownedRuntime || !session || !ownedRuntime.abandonServerRequest(sessionId, requestId))
|
|
578
|
+
return;
|
|
579
|
+
void ownedRuntime
|
|
580
|
+
.recycle(session, new Date().toISOString())
|
|
581
|
+
.then((recovered) => saveSession(recovered))
|
|
582
|
+
.catch(() => {
|
|
583
|
+
// The durable attention survives; normal session recovery can reacquire the writer.
|
|
584
|
+
});
|
|
585
|
+
}, options.attentionAcknowledgementDeadlineMs ?? 5_000);
|
|
586
|
+
handle.unref?.();
|
|
587
|
+
attentionAcknowledgementTimers.set(key, { handle, token });
|
|
588
|
+
};
|
|
554
589
|
const failPendingCheckpointHandoffs = (sessionId, recoverBoundary) => {
|
|
555
590
|
for (const interaction of interactions.list(sessionId)) {
|
|
556
591
|
if (interaction.kind !== 'orgPlanCheckpoint')
|
|
@@ -568,28 +603,38 @@ export async function composeRelayApp(options) {
|
|
|
568
603
|
* enters the durable store, requests human attention, and notifies autopilot.
|
|
569
604
|
* Both the codex JSON-RPC handler and the kimi polling handler land here.
|
|
570
605
|
*/
|
|
571
|
-
const acceptPendingInteraction = (sessionId, rawInteraction, session) => {
|
|
606
|
+
const acceptPendingInteraction = (sessionId, rawInteraction, session, acknowledgeAttention = false) => {
|
|
572
607
|
const interaction = {
|
|
573
608
|
...rawInteraction,
|
|
574
609
|
turnId: session.activeTurnId,
|
|
575
610
|
requestedAt: new Date().toISOString(),
|
|
576
611
|
};
|
|
577
|
-
interactions.add(sessionId, interaction);
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
612
|
+
interactions.add(sessionId, interaction, acknowledgeAttention && interaction.kind === 'orgPlanAttention' ? 'acknowledged' : 'active');
|
|
613
|
+
try {
|
|
614
|
+
activity.observe({
|
|
615
|
+
sessionId,
|
|
616
|
+
occurredAt: interaction.requestedAt,
|
|
617
|
+
kind: 'interactionPending',
|
|
618
|
+
...(interaction.kind === 'orgPlanAttention'
|
|
619
|
+
? { attentionReason: interaction.payload.reason }
|
|
620
|
+
: {}),
|
|
621
|
+
});
|
|
622
|
+
autopilot.semanticEvent(sessionId, 'interactionChanged');
|
|
623
|
+
const updated = RelaySession.rehydrate(session).requestInteraction(interaction, new Date().toISOString()).snapshot;
|
|
624
|
+
sessions.save(updated);
|
|
625
|
+
events.publish(journal.append(sessionId, 'interaction.requested', interaction, updated.updatedAt));
|
|
626
|
+
if (interaction.kind === 'orgPlanAttention')
|
|
627
|
+
events.publish(journal.append(sessionId, 'org-plan.attention-required', interaction, updated.updatedAt));
|
|
628
|
+
autopilot.evaluate(sessionId);
|
|
629
|
+
}
|
|
630
|
+
catch (error) {
|
|
631
|
+
if (!acknowledgeAttention || interaction.kind !== 'orgPlanAttention')
|
|
632
|
+
throw error;
|
|
633
|
+
attentionDiagnostic('%o', {
|
|
634
|
+
requestId: interaction.requestId,
|
|
635
|
+
reasonCode: 'postPersistenceProjectionFailed',
|
|
636
|
+
});
|
|
637
|
+
}
|
|
593
638
|
return true;
|
|
594
639
|
};
|
|
595
640
|
const acceptPlanUpdate = (sessionId, update) => {
|
|
@@ -625,7 +670,9 @@ export async function composeRelayApp(options) {
|
|
|
625
670
|
if (resolvedRequestId) {
|
|
626
671
|
const interaction = interactions.find(sessionId, resolvedRequestId);
|
|
627
672
|
const outcome = interaction?.kind === 'orgPlanAttention' ? 'failed' : 'dismissed';
|
|
628
|
-
if (interaction?.kind
|
|
673
|
+
if (!(interaction?.kind === 'orgPlanAttention' &&
|
|
674
|
+
interactions.resolutionState(sessionId, resolvedRequestId) === 'acknowledged') &&
|
|
675
|
+
interaction?.kind !== 'quiz' &&
|
|
629
676
|
interactions.resolve(sessionId, resolvedRequestId, occurredAt, outcome)) {
|
|
630
677
|
if (outcome === 'failed')
|
|
631
678
|
publishAttentionSettlement(sessionId, resolvedRequestId, occurredAt, outcome);
|
|
@@ -683,7 +730,7 @@ export async function composeRelayApp(options) {
|
|
|
683
730
|
const retained = supervisedPlans.find(sessionId);
|
|
684
731
|
const identity = supervisedPlans.identity(sessionId);
|
|
685
732
|
const checkpoint = retained && identity
|
|
686
|
-
? resolveOrgPlanCheckpointSignal(compactCheckpointKind, retained, identity)
|
|
733
|
+
? resolveOrgPlanCheckpointSignal(compactCheckpointKind, retained, identity, supervisedPlans.publicationReason(sessionId))
|
|
687
734
|
: null;
|
|
688
735
|
if (checkpoint)
|
|
689
736
|
rawInteraction = {
|
|
@@ -741,23 +788,38 @@ export async function composeRelayApp(options) {
|
|
|
741
788
|
!identity ||
|
|
742
789
|
checkpoint.planIdentity !== identity ||
|
|
743
790
|
!rootOwned)
|
|
744
|
-
return (runtime?.resolveServerRequest(sessionId, rawInteraction.requestId,
|
|
745
|
-
|
|
791
|
+
return (runtime?.resolveServerRequest(sessionId, rawInteraction.requestId, checkpointFailureResponse(!checkpoint
|
|
792
|
+
? compactCheckpointKind === 'l1Accepted'
|
|
793
|
+
? 'acceptedL1NotResolved'
|
|
794
|
+
: 'checkpointPayloadInvalid'
|
|
795
|
+
: !retained
|
|
796
|
+
? 'planProjectionUnavailable'
|
|
797
|
+
: !identity || checkpoint.planIdentity !== identity
|
|
798
|
+
? 'planIdentityMismatch'
|
|
799
|
+
: 'rootNotOwner')) === true);
|
|
800
|
+
const validation = validateOrgPlanCheckpoint({
|
|
746
801
|
checkpoint,
|
|
747
802
|
plan: retained,
|
|
748
803
|
planIdentity: identity,
|
|
749
804
|
rootOwned,
|
|
750
805
|
hasActiveL1Writer: (position) => hasActiveL1Writer(activity.snapshot(sessionId, new Date().toISOString()), position),
|
|
751
806
|
});
|
|
752
|
-
if (!valid || !session.activeTurnId)
|
|
753
|
-
return (runtime?.resolveServerRequest(sessionId, rawInteraction.requestId,
|
|
807
|
+
if (!validation.valid || !session.activeTurnId)
|
|
808
|
+
return (runtime?.resolveServerRequest(sessionId, rawInteraction.requestId, checkpointFailureResponse(validation.valid ? 'rootTurnNotActive' : validation.reasonCode, {
|
|
809
|
+
...(!validation.valid && validation.expected
|
|
810
|
+
? { expected: validation.expected }
|
|
811
|
+
: {}),
|
|
812
|
+
...(!validation.valid && validation.observed
|
|
813
|
+
? { observed: validation.observed }
|
|
814
|
+
: {}),
|
|
815
|
+
})) === true);
|
|
754
816
|
let recordStatus = 'failed';
|
|
755
817
|
try {
|
|
756
818
|
recordStatus = autopilot.checkpointAccepted(sessionId, checkpoint, session.activeTurnId, new Date().toISOString());
|
|
757
819
|
}
|
|
758
820
|
catch {
|
|
759
821
|
if (!autopilot.checkpointHandoffFailed(sessionId, session.activeTurnId))
|
|
760
|
-
return (runtime?.resolveServerRequest(sessionId, rawInteraction.requestId,
|
|
822
|
+
return (runtime?.resolveServerRequest(sessionId, rawInteraction.requestId, checkpointFailureResponse('checkpointPersistenceFailed')) === true);
|
|
761
823
|
const interaction = {
|
|
762
824
|
...rawInteraction,
|
|
763
825
|
turnId: session.activeTurnId,
|
|
@@ -768,7 +830,7 @@ export async function composeRelayApp(options) {
|
|
|
768
830
|
return true;
|
|
769
831
|
}
|
|
770
832
|
if (recordStatus === 'failed')
|
|
771
|
-
return (runtime?.resolveServerRequest(sessionId, rawInteraction.requestId,
|
|
833
|
+
return (runtime?.resolveServerRequest(sessionId, rawInteraction.requestId, checkpointFailureResponse('checkpointStateConflict')) === true);
|
|
772
834
|
if (recordStatus === 'alreadyRecorded')
|
|
773
835
|
return (runtime?.resolveServerRequest(sessionId, rawInteraction.requestId, toOrgPlanCheckpointToolResponse('alreadyRecorded')) === true);
|
|
774
836
|
const interaction = {
|
|
@@ -788,6 +850,24 @@ export async function composeRelayApp(options) {
|
|
|
788
850
|
// is cancelled solely by the JSON-RPC write-settlement callback.
|
|
789
851
|
return true;
|
|
790
852
|
}
|
|
853
|
+
if (rawInteraction.kind === 'orgPlanAttention') {
|
|
854
|
+
try {
|
|
855
|
+
acceptPendingInteraction(sessionId, rawInteraction, session, true);
|
|
856
|
+
const acknowledged = runtime?.resolveServerRequest(sessionId, rawInteraction.requestId, toOrgPlanAttentionAcknowledgement()) === true;
|
|
857
|
+
if (acknowledged)
|
|
858
|
+
armAttentionAcknowledgementDeadline(sessionId, rawInteraction.requestId);
|
|
859
|
+
return acknowledged;
|
|
860
|
+
}
|
|
861
|
+
catch {
|
|
862
|
+
const rejected = runtime?.resolveServerRequest(sessionId, rawInteraction.requestId, toOrgPlanAttentionAcknowledgement({
|
|
863
|
+
accepted: false,
|
|
864
|
+
reason: 'persistenceFailed',
|
|
865
|
+
})) === true;
|
|
866
|
+
if (rejected)
|
|
867
|
+
armAttentionAcknowledgementDeadline(sessionId, rawInteraction.requestId);
|
|
868
|
+
return rejected;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
791
871
|
return acceptPendingInteraction(sessionId, rawInteraction, session);
|
|
792
872
|
}, (sessionId) => {
|
|
793
873
|
activity.disconnected(sessionId, new Date().toISOString());
|
|
@@ -827,7 +907,18 @@ export async function composeRelayApp(options) {
|
|
|
827
907
|
const handleKimiServerRequest = (sessionId, request) => {
|
|
828
908
|
const rawInteraction = toPendingInteraction(request);
|
|
829
909
|
const session = withPendingInteractions(sessions.find(sessionId));
|
|
830
|
-
|
|
910
|
+
if (!rawInteraction || !session)
|
|
911
|
+
return false;
|
|
912
|
+
if (rawInteraction.kind !== 'orgPlanAttention')
|
|
913
|
+
return acceptPendingInteraction(sessionId, rawInteraction, session);
|
|
914
|
+
try {
|
|
915
|
+
acceptPendingInteraction(sessionId, rawInteraction, session, true);
|
|
916
|
+
void kimiRuntime?.resolveServerRequest(sessionId, rawInteraction.requestId, toOrgPlanAttentionAcknowledgement());
|
|
917
|
+
}
|
|
918
|
+
catch {
|
|
919
|
+
void kimiRuntime?.resolveServerRequest(sessionId, rawInteraction.requestId, toOrgPlanAttentionAcknowledgement({ accepted: false, reason: 'persistenceFailed' }));
|
|
920
|
+
}
|
|
921
|
+
return true;
|
|
831
922
|
};
|
|
832
923
|
kimiRuntime = kimiManager
|
|
833
924
|
? new KimiSessionRuntime({
|
|
@@ -850,6 +941,10 @@ export async function composeRelayApp(options) {
|
|
|
850
941
|
})
|
|
851
942
|
: null;
|
|
852
943
|
runtime?.onServerResponseSettled((sessionId, requestId, outcome) => {
|
|
944
|
+
if (attentionAcknowledgementTimers.has(checkpointTimerKey(sessionId, requestId))) {
|
|
945
|
+
cancelAttentionAcknowledgementTimer(sessionId, requestId);
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
853
948
|
const interaction = interactions.find(sessionId, requestId);
|
|
854
949
|
if (interaction?.kind !== 'orgPlanCheckpoint')
|
|
855
950
|
return;
|
|
@@ -1336,6 +1431,10 @@ export async function composeRelayApp(options) {
|
|
|
1336
1431
|
const interaction = interactions.find(sessionId, requestId);
|
|
1337
1432
|
if (!interaction || interaction.kind !== 'orgPlanAttention')
|
|
1338
1433
|
return { kind: 'noActive' };
|
|
1434
|
+
const decision = parseOrgPlanAttentionToolResponse(response);
|
|
1435
|
+
const attention = parseOrgPlanAttention(interaction.payload);
|
|
1436
|
+
if (!decision || !attention)
|
|
1437
|
+
return { kind: 'staleOperation' };
|
|
1339
1438
|
const claim = interactions.claimOperation(sessionId, requestId, operationKey);
|
|
1340
1439
|
if (claim === 'resolved') {
|
|
1341
1440
|
const resolved = interactions.resolved(sessionId, requestId);
|
|
@@ -1347,6 +1446,29 @@ export async function composeRelayApp(options) {
|
|
|
1347
1446
|
return { kind: 'staleOperation' };
|
|
1348
1447
|
if (claim === 'missing')
|
|
1349
1448
|
return { kind: 'noActive' };
|
|
1449
|
+
const acknowledged = interactions.resolutionState(sessionId, requestId) === 'acknowledged';
|
|
1450
|
+
if (acknowledged) {
|
|
1451
|
+
if (!interactions.beginDelivery(sessionId, requestId, operationKey))
|
|
1452
|
+
return { kind: 'staleOperation' };
|
|
1453
|
+
if (decision.action === 'resume' && attention.executorReplacement) {
|
|
1454
|
+
const authorization = autopilot.authorizeExecutorReplacement(sessionId, attention.executorReplacement.canonicalTaskName);
|
|
1455
|
+
if (!authorization.accepted) {
|
|
1456
|
+
interactions.retryDelivery(sessionId, requestId, operationKey);
|
|
1457
|
+
return { kind: 'replacementRejected' };
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
const resolvedAt = new Date().toISOString();
|
|
1461
|
+
if (!interactions.settleOperation(sessionId, requestId, operationKey, resolvedAt, 'answered'))
|
|
1462
|
+
return { kind: 'staleOperation' };
|
|
1463
|
+
const accepted = { kind: 'accepted', resolvedAt };
|
|
1464
|
+
idempotency.put(scope, operationKey, 202, JSON.stringify({ kind: 'replayed', resolvedAt }));
|
|
1465
|
+
publishAttentionSettlement(sessionId, requestId, resolvedAt, 'answered');
|
|
1466
|
+
if (decision.action === 'disableAutopilot')
|
|
1467
|
+
autopilot.disable(sessionId);
|
|
1468
|
+
else
|
|
1469
|
+
autopilot.enable(sessionId);
|
|
1470
|
+
return accepted;
|
|
1471
|
+
}
|
|
1350
1472
|
// A durable capability belongs to the session/thread, not the
|
|
1351
1473
|
// relay process. A supported stopped writer is retryable;
|
|
1352
1474
|
// missing capability identifies a pre-rollout legacy thread.
|
|
@@ -1356,6 +1478,13 @@ export async function composeRelayApp(options) {
|
|
|
1356
1478
|
return { kind: 'writerUnavailable' };
|
|
1357
1479
|
if (!interactions.beginDelivery(sessionId, requestId, operationKey))
|
|
1358
1480
|
return { kind: 'staleOperation' };
|
|
1481
|
+
if (decision.action === 'resume' && attention.executorReplacement) {
|
|
1482
|
+
const authorization = autopilot.authorizeExecutorReplacement(sessionId, attention.executorReplacement.canonicalTaskName);
|
|
1483
|
+
if (!authorization.accepted) {
|
|
1484
|
+
interactions.retryDelivery(sessionId, requestId, operationKey);
|
|
1485
|
+
return { kind: 'replacementRejected' };
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1359
1488
|
const writer = runtime.attentionWriterState(sessionId, requestId);
|
|
1360
1489
|
if (writer === 'unavailable') {
|
|
1361
1490
|
interactions.retryDelivery(sessionId, requestId, operationKey);
|
|
@@ -1379,6 +1508,10 @@ export async function composeRelayApp(options) {
|
|
|
1379
1508
|
const accepted = { kind: 'accepted', resolvedAt };
|
|
1380
1509
|
idempotency.put(scope, operationKey, 202, JSON.stringify({ kind: 'replayed', resolvedAt }));
|
|
1381
1510
|
publishAttentionSettlement(sessionId, requestId, resolvedAt, 'answered');
|
|
1511
|
+
if (decision.action === 'disableAutopilot')
|
|
1512
|
+
autopilot.disable(sessionId);
|
|
1513
|
+
else
|
|
1514
|
+
autopilot.enable(sessionId);
|
|
1382
1515
|
return accepted;
|
|
1383
1516
|
})();
|
|
1384
1517
|
attentionResolutionOperations.set(key, operation);
|
|
@@ -1468,6 +1601,14 @@ export async function composeRelayApp(options) {
|
|
|
1468
1601
|
checkpointHandoffTimers.delete(key);
|
|
1469
1602
|
}
|
|
1470
1603
|
}
|
|
1604
|
+
for (const key of [...attentionAcknowledgementTimers.keys()]) {
|
|
1605
|
+
if (key.startsWith(`${session.id}:`)) {
|
|
1606
|
+
const timer = attentionAcknowledgementTimers.get(key);
|
|
1607
|
+
if (timer)
|
|
1608
|
+
clearTimeout(timer.handle);
|
|
1609
|
+
attentionAcknowledgementTimers.delete(key);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1471
1612
|
// Relay shutdown only releases this process's writer. A typed attention
|
|
1472
1613
|
// request remains a durable human-visible blocker for the next relay
|
|
1473
1614
|
// instance; only an app-server-cleared request is a failed audit outcome.
|
|
@@ -1497,7 +1638,8 @@ function resolveStateDatabasePath(root, stateHome) {
|
|
|
1497
1638
|
const legacyPath = legacyRelayStatePath(root, stateHome);
|
|
1498
1639
|
return existsSync(legacyPath) ? legacyPath : currentPath;
|
|
1499
1640
|
}
|
|
1500
|
-
function
|
|
1501
|
-
|
|
1502
|
-
|
|
1641
|
+
function checkpointFailureResponse(reasonCode, details = {}) {
|
|
1642
|
+
const correlationId = randomUUID();
|
|
1643
|
+
checkpointDiagnostic('%o', { correlationId, reasonCode, ...details });
|
|
1644
|
+
return toOrgPlanCheckpointToolResponse('failed', { reasonCode, correlationId, ...details });
|
|
1503
1645
|
}
|
|
@@ -201,7 +201,10 @@ function childOutcome(status, action, previous) {
|
|
|
201
201
|
function childState(status, action, previous = 'working') {
|
|
202
202
|
if (status === 'error' || status === 'failed' || status === 'systemError' || status === 'errored')
|
|
203
203
|
return 'blocked';
|
|
204
|
-
if (status === 'completed' ||
|
|
204
|
+
if (status === 'completed' ||
|
|
205
|
+
status === 'idle' ||
|
|
206
|
+
status === 'interrupted' ||
|
|
207
|
+
action === 'close_agent')
|
|
205
208
|
return 'idle';
|
|
206
209
|
// `wait` belongs to the caller/root; it does not rewrite the child which may
|
|
207
210
|
// still be working. A child can explicitly report its own waiting status.
|
|
@@ -229,7 +232,10 @@ function childReason(status, action) {
|
|
|
229
232
|
return 'processExited';
|
|
230
233
|
if (action === 'wait')
|
|
231
234
|
return 'collaborationWait';
|
|
232
|
-
if (status === 'completed' ||
|
|
235
|
+
if (status === 'completed' ||
|
|
236
|
+
status === 'idle' ||
|
|
237
|
+
status === 'interrupted' ||
|
|
238
|
+
action === 'close_agent')
|
|
233
239
|
return 'turnCompleted';
|
|
234
240
|
return 'turnActive';
|
|
235
241
|
}
|
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
* Designed by Denis Roio <jaromil@dyne.org>
|
|
4
4
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
*/
|
|
6
|
-
export const AUTOPILOT_PROMPT_VERSION = '
|
|
7
|
-
export const AUTOPILOT_CONTINUATION_PROMPT = 'Inspect the active supervised Org Plan. Refer to every L1 as L<a> and each nested L2 as L<a>.<b>, using one-based positions. Spawn exactly one executor per L1 and pass the exact literal task_name l<a>: l1 for L1, l2 for L2, and so on. Never create an L2-specific task name or append a title, role, nickname, plan name, or generated label. A validated DONE L2 and an accepted L1 are mandatory answer boundaries. The corresponding gestalt_org_plan_checkpoint call must be the last tool call of that root turn: immediately emit exactly one boundary final and end the turn, without followup_task, review, executor launch, or later milestone work. Roll up commentary, files, verification, and commands since the previous boundary into that one answer; all later milestone activity belongs to a new root turn. On this later Autopilot turn, resume the same executor after an L2 boundary or launch the next
|
|
6
|
+
export const AUTOPILOT_PROMPT_VERSION = 'v13';
|
|
7
|
+
export const AUTOPILOT_CONTINUATION_PROMPT = 'Inspect the active supervised Org Plan. Refer to every L1 as L<a> and each nested L2 as L<a>.<b>, using one-based positions. Spawn exactly one executor per L1 and pass the exact literal task_name l<a>: l1 for L1, l2 for L2, and so on. Never create an L2-specific task name or append a title, role, nickname, plan name, or generated label. A validated DONE L2 and an accepted L1 are mandatory answer boundaries. The corresponding gestalt_org_plan_checkpoint call must be the last tool call of that root turn: immediately emit exactly one boundary final and end the turn, without followup_task, review, executor launch, or later milestone work. An accepted L1 always ends the root turn with a chat answer: never continue into the next L1 or emit post-acceptance documentation as commentary. Roll up commentary, files, verification, and commands since the previous boundary into that one answer; all later milestone activity belongs to a new root turn. On this later Autopilot turn, resume the same executor after an L2 boundary or launch exactly one fresh canonical executor for the next L1 after an accepted-L1 boundary. Treat a status question as an interruption: answer briefly, then perform the applicable continuation in the same turn. Before sending any blocker response or yielding because progress cannot continue safely, call gestalt_org_plan_attention for the matching decision-table blocker; never rely on blocker prose as the signal. When explicit human permission is required for a physical executor replacement, include executorReplacement with the exact canonicalTaskName; Mobile alone computes and persists the physical generation. Do not call attention for routine progress or a recoverable failure. Before yielding for known long work, call gestalt_autopilot_wait_lease version 2 with relevant observable wake conditions and a bounded maxWaitMs; it is one episode, so reassess before registering another in a later turn. Yield only when its response contains accepted:true; accepted:false means no continuation was registered and requires same-turn supervision. For GitHub PR CI, start an owned gh pr checks --watch process and lease processExited plus processResultAvailable while it runs. When the Autopilot probe is active, register the compatible wait lease, declare genuine attention, or immediately do actionable work; never acknowledge waiting in prose. Do not send a status-only response.';
|
|
8
8
|
export const AUTOPILOT_EXECUTOR_CONTINUATION_PROMPT = 'Continue the same assigned Org L1 from its durable state. A prior turn ending did not complete the objective. Consume any supplied process result and take the next legal L2 action. Whenever an L2 reaches DONE, return its concise structured evidence and end the executor turn so the root can publish that mandatory answer boundary. Also report at the L1 review boundary or through structured attention.';
|
|
9
9
|
/** Builds a physical launch instruction while keeping durable L1 identity canonical. */
|
|
10
10
|
export function autopilotExecutorLaunchPrompt(identity) {
|
|
11
|
-
const launch = `Launch task_name ${identity.taskName} for canonical ${identity.canonicalPosition};
|
|
11
|
+
const launch = `Launch task_name ${identity.taskName} for canonical ${identity.canonicalPosition} with agent_type org-plan-executor and reasoning_effort high; the profile selects gpt-5.6-terra by default, so do not override its model. Retain the canonical label in status and review output.`;
|
|
12
12
|
if (identity.generation === 1)
|
|
13
13
|
return `${AUTOPILOT_CONTINUATION_PROMPT} ${launch}`;
|
|
14
|
-
return `${AUTOPILOT_CONTINUATION_PROMPT} ${launch} This exact replacement generation ${identity.generation} is durably authorized by Mobile; do not infer another generation from this prompt or the roster; the durable ${identity.canonicalTaskName} slot may remain reserved. Do not reuse that task_name or attempt to change an existing agent's model in place. Interrupt the previous physical executor if it is still running, then spawn ${identity.taskName}
|
|
14
|
+
return `${AUTOPILOT_CONTINUATION_PROMPT} ${launch} This exact replacement generation ${identity.generation} is durably authorized by Mobile; do not infer another generation from this prompt or the roster; the durable ${identity.canonicalTaskName} slot may remain reserved. Do not reuse that task_name or attempt to change an existing agent's model in place. Interrupt the previous physical executor if it is still running, then spawn the authorized ${identity.taskName}. Transfer sole ${identity.canonicalPosition} ownership and continue from its durable Org Plan and worktree state.`;
|
|
15
15
|
}
|
|
16
16
|
export const defaultAutopilotPolicy = Object.freeze({
|
|
17
17
|
quiescenceMs: 1_000,
|