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.
@@ -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, validOrgPlanCheckpoint, } from './features/org-plan-checkpoint/application/validate.js';
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
- activity.observe({
579
- sessionId,
580
- occurredAt: interaction.requestedAt,
581
- kind: 'interactionPending',
582
- ...(interaction.kind === 'orgPlanAttention'
583
- ? { attentionReason: interaction.payload.reason }
584
- : {}),
585
- });
586
- autopilot.semanticEvent(sessionId, 'interactionChanged');
587
- const updated = RelaySession.rehydrate(session).requestInteraction(interaction, new Date().toISOString()).snapshot;
588
- sessions.save(updated);
589
- events.publish(journal.append(sessionId, 'interaction.requested', interaction, updated.updatedAt));
590
- if (interaction.kind === 'orgPlanAttention')
591
- events.publish(journal.append(sessionId, 'org-plan.attention-required', interaction, updated.updatedAt));
592
- autopilot.evaluate(sessionId);
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 !== 'quiz' &&
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, toOrgPlanCheckpointToolResponse('failed')) === true);
745
- const valid = validOrgPlanCheckpoint({
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, toOrgPlanCheckpointToolResponse('failed')) === true);
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, toOrgPlanCheckpointToolResponse('failed')) === true);
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, toOrgPlanCheckpointToolResponse('failed')) === true);
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
- return Boolean(rawInteraction && session && acceptPendingInteraction(sessionId, rawInteraction, session));
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 hasActiveL1Writer(snapshot, position) {
1501
- return snapshot.subagents.some((child) => child.canonicalPosition === position &&
1502
- ['working', 'awaitingAgent', 'awaitingHuman'].includes(child.state));
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' || status === 'idle' || action === 'close_agent')
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' || status === 'idle' || action === 'close_agent')
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 = 'v11';
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 executor 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. Do not call it 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.';
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}; retain the canonical label in status and review output.`;
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} with agent_type worker and an explicit model selected by the supervisor. Transfer sole ${identity.canonicalPosition} ownership and continue from its durable Org Plan and worktree state.`;
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,