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
package/dist/client/index.html
CHANGED
|
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
16
16
|
<link rel="icon" href="/icons/gestalt-mobile-192.png" />
|
|
17
17
|
<link rel="apple-touch-icon" href="/icons/gestalt-mobile-180.png" />
|
|
18
18
|
<title>Gestalt Mobile</title>
|
|
19
|
-
<script type="module" crossorigin src="/assets/index-
|
|
19
|
+
<script type="module" crossorigin src="/assets/index-BRly-q56.js"></script>
|
|
20
20
|
<link rel="stylesheet" crossorigin href="/assets/index-D57mr3B1.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
@@ -15,8 +15,11 @@ import { CliUsageError, parseConfig } from './config.js';
|
|
|
15
15
|
import { LauncherProfileCatalog } from './platform/catalog/launcher-profile-catalog.js';
|
|
16
16
|
import { FilesystemSkillProfileStore } from './platform/skills/filesystem-skill-profile-store.js';
|
|
17
17
|
import { loadPwaIcon } from './platform/pwa/load-pwa-icon.js';
|
|
18
|
+
import { exportControlPlaneTrace, formatControlPlaneTrace, } from './features/control-plane-trace/export-trace.js';
|
|
19
|
+
import { relayStatePath } from './platform/persistence/state-path.js';
|
|
18
20
|
const runFile = promisify(execFile);
|
|
19
21
|
export const usage = `Usage: gestalt-mobile [options]
|
|
22
|
+
gestalt-mobile trace <session-id> [--json] [--cwd <path>] [--data-dir <path>]
|
|
20
23
|
|
|
21
24
|
Options:
|
|
22
25
|
--cwd <path> Workspace root (default: current directory)
|
|
@@ -125,6 +128,33 @@ export function installShutdownHandlers(app, signalSource = process) {
|
|
|
125
128
|
return shutdown;
|
|
126
129
|
}
|
|
127
130
|
function parseInvocation(args, cwd) {
|
|
131
|
+
if (args[0] === 'trace') {
|
|
132
|
+
const sessionId = args[1];
|
|
133
|
+
if (!sessionId || sessionId.startsWith('--'))
|
|
134
|
+
throw new CliUsageError('trace requires a session ID');
|
|
135
|
+
let json = false;
|
|
136
|
+
let root = cwd;
|
|
137
|
+
let dataDir;
|
|
138
|
+
for (let index = 2; index < args.length; index += 1) {
|
|
139
|
+
const option = args[index];
|
|
140
|
+
if (option === '--json') {
|
|
141
|
+
if (json)
|
|
142
|
+
throw new CliUsageError('Duplicate option: --json');
|
|
143
|
+
json = true;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (option !== '--cwd' && option !== '--data-dir')
|
|
147
|
+
throw new CliUsageError(`Unknown trace option: ${option}`);
|
|
148
|
+
const value = args[++index];
|
|
149
|
+
if (!value || value.startsWith('--'))
|
|
150
|
+
throw new CliUsageError(`Missing value for ${option}`);
|
|
151
|
+
if (option === '--cwd')
|
|
152
|
+
root = resolve(cwd, value);
|
|
153
|
+
else
|
|
154
|
+
dataDir = resolve(cwd, value);
|
|
155
|
+
}
|
|
156
|
+
return { command: 'trace', sessionId, json, root, ...(dataDir ? { dataDir } : {}) };
|
|
157
|
+
}
|
|
128
158
|
if (args.includes('--help')) {
|
|
129
159
|
if (args.length !== 1)
|
|
130
160
|
throw new CliUsageError('--help cannot be combined with other arguments');
|
|
@@ -187,6 +217,23 @@ export async function runCli(dependencies = {}) {
|
|
|
187
217
|
stdout.write(`${await packageVersion(moduleUrl)}\n`);
|
|
188
218
|
return 0;
|
|
189
219
|
}
|
|
220
|
+
if (invocation.command === 'trace') {
|
|
221
|
+
const stateHome = dependencies.environment?.XDG_STATE_HOME ??
|
|
222
|
+
process.env.XDG_STATE_HOME ??
|
|
223
|
+
resolve(dependencies.homeDirectory ?? homedir(), '.local/state');
|
|
224
|
+
const databasePath = invocation.dataDir
|
|
225
|
+
? resolve(invocation.dataDir, 'relay.sqlite')
|
|
226
|
+
: relayStatePath(invocation.root, stateHome);
|
|
227
|
+
try {
|
|
228
|
+
const trace = (dependencies.exportTrace ?? exportControlPlaneTrace)(databasePath, invocation.sessionId);
|
|
229
|
+
stdout.write(invocation.json ? `${JSON.stringify(trace, null, 2)}\n` : formatControlPlaneTrace(trace));
|
|
230
|
+
return 0;
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
stderr.write(`Unable to export control-plane trace: ${error instanceof Error ? error.message : 'unknown error'}\n`);
|
|
234
|
+
return 1;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
190
237
|
const skillProfiles = new FilesystemSkillProfileStore(dependencies.homeDirectory ?? homedir());
|
|
191
238
|
if (invocation.command === 'list')
|
|
192
239
|
return listSkillProfiles(skillProfiles, stdout, stderr);
|
|
@@ -12,7 +12,7 @@ import { join } from 'node:path';
|
|
|
12
12
|
import { describe, expect, it, onTestFinished, vi } from 'vitest';
|
|
13
13
|
import WebSocket from 'ws';
|
|
14
14
|
import { composeRelayApp } from './composition.js';
|
|
15
|
-
import {
|
|
15
|
+
import { autopilotExecutorLaunchPrompt } from './features/autopilot/application/policy.js';
|
|
16
16
|
import { CodexJsonRpcError } from './platform/codex/json-rpc-client.js';
|
|
17
17
|
import { SqliteAuthorizationStore } from './platform/auth/sqlite-authorization-store.js';
|
|
18
18
|
import { authorizationSessionId, authorizedDeviceId, localOwnerId, webAuthnCredentialId, } from './features/auth/domain/identifiers.js';
|
|
@@ -497,7 +497,12 @@ describe('production composition', () => {
|
|
|
497
497
|
input: [
|
|
498
498
|
{
|
|
499
499
|
type: 'text',
|
|
500
|
-
text:
|
|
500
|
+
text: autopilotExecutorLaunchPrompt({
|
|
501
|
+
canonicalTaskName: 'l1',
|
|
502
|
+
canonicalPosition: 'L1',
|
|
503
|
+
generation: 1,
|
|
504
|
+
taskName: 'l1',
|
|
505
|
+
}),
|
|
501
506
|
text_elements: [],
|
|
502
507
|
},
|
|
503
508
|
],
|
|
@@ -1040,6 +1045,16 @@ describe('production composition', () => {
|
|
|
1040
1045
|
await expect
|
|
1041
1046
|
.poll(async () => (await fixture.app.inject(`/api/sessions/${fixture.sessionId}`)).json().activeTurnId)
|
|
1042
1047
|
.toBeNull();
|
|
1048
|
+
await expect
|
|
1049
|
+
.poll(() => {
|
|
1050
|
+
const database = new DatabaseSync(join(fixture.dataDir, 'relay.sqlite'));
|
|
1051
|
+
const scheduled = database
|
|
1052
|
+
.prepare("SELECT count(*) AS count FROM session_events WHERE session_id = ? AND type = 'autopilot.continuation-scheduled'")
|
|
1053
|
+
.get(fixture.sessionId);
|
|
1054
|
+
database.close();
|
|
1055
|
+
return scheduled.count;
|
|
1056
|
+
})
|
|
1057
|
+
.toBeGreaterThan(0);
|
|
1043
1058
|
const startedTurns = () => fixture.handles
|
|
1044
1059
|
.flatMap((candidate) => candidate.requests)
|
|
1045
1060
|
.filter((request) => request.method === 'turn/start').length;
|
|
@@ -1455,7 +1470,7 @@ describe('production composition', () => {
|
|
|
1455
1470
|
.filter((request) => request.method === 'turn/start')).toHaveLength(startsAfterFinal);
|
|
1456
1471
|
await fixture.app.close();
|
|
1457
1472
|
});
|
|
1458
|
-
it('production attention
|
|
1473
|
+
it('production attention approval resumes Autopilot and a complete plan transitions to completed', async () => {
|
|
1459
1474
|
const fixture = await createProductionAutopilotFixture();
|
|
1460
1475
|
const handle = fixture.handles.find((candidate) => candidate.request);
|
|
1461
1476
|
expect((await fixture.app.inject({
|
|
@@ -1474,15 +1489,6 @@ describe('production composition', () => {
|
|
|
1474
1489
|
url: `/api/sessions/${fixture.sessionId}/attention/810/resolve`,
|
|
1475
1490
|
payload: { operationKey: 'resume-attention', action: 'resume' },
|
|
1476
1491
|
})).statusCode).toBe(202);
|
|
1477
|
-
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
1478
|
-
expect((await fixture.app.inject(`/api/sessions/${fixture.sessionId}`)).json()).toMatchObject({
|
|
1479
|
-
autopilot: { state: 'attentionRequired', enabled: false },
|
|
1480
|
-
});
|
|
1481
|
-
expect((await fixture.app.inject({
|
|
1482
|
-
method: 'PUT',
|
|
1483
|
-
url: `/api/sessions/${fixture.sessionId}/autopilot`,
|
|
1484
|
-
payload: { enabled: true },
|
|
1485
|
-
})).statusCode).toBe(200);
|
|
1486
1492
|
await vi.waitFor(async () => expect((await fixture.app.inject(`/api/sessions/${fixture.sessionId}`)).json()).toMatchObject({
|
|
1487
1493
|
autopilot: { enabled: true, state: expect.stringMatching(/monitoring|backoff/) },
|
|
1488
1494
|
}));
|
|
@@ -1615,8 +1621,14 @@ describe('production composition', () => {
|
|
|
1615
1621
|
handle.notify(completion);
|
|
1616
1622
|
handle.notify(completion);
|
|
1617
1623
|
await vi.waitFor(() => expect(timers.filter((timer) => !timer.cancelled && !timer.fired)).toHaveLength(1));
|
|
1618
|
-
|
|
1619
|
-
|
|
1624
|
+
for (let attempts = 0; attempts < 3; attempts += 1) {
|
|
1625
|
+
const pending = timers.find((timer) => !timer.cancelled && !timer.fired);
|
|
1626
|
+
if (!pending)
|
|
1627
|
+
break;
|
|
1628
|
+
pending.fired = true;
|
|
1629
|
+
pending.callback();
|
|
1630
|
+
await Promise.resolve();
|
|
1631
|
+
}
|
|
1620
1632
|
await vi.waitFor(() => expect(fixture.handles
|
|
1621
1633
|
.flatMap((candidate) => candidate.calls)
|
|
1622
1634
|
.filter((call) => call === 'turn/start')).toHaveLength(2), { timeout: 3_500 });
|
|
@@ -1770,7 +1782,7 @@ describe('production composition', () => {
|
|
|
1770
1782
|
// rejected child continuation; it must never target the dead child.
|
|
1771
1783
|
expect((starts[3]?.params).threadId).not.toBe('child-1');
|
|
1772
1784
|
expect((starts[3]?.params).input?.[0]?.text).toContain('Launch task_name l1_g2 for canonical L1');
|
|
1773
|
-
expect((starts[3]?.params).input?.[0]?.text).toContain('
|
|
1785
|
+
expect((starts[3]?.params).input?.[0]?.text).toContain('agent_type org-plan-executor and reasoning_effort high');
|
|
1774
1786
|
// Capacity recovery is allowed only for the active root control. It
|
|
1775
1787
|
// recycles that writer but must retain the one durable g2 handoff.
|
|
1776
1788
|
const rootHandle = fixture.handles.find((candidate) => candidate.requests.some((request) => request.method === 'turn/start' &&
|
|
@@ -66,7 +66,7 @@ import { AutopilotCoordinator } from './features/autopilot/application/service.j
|
|
|
66
66
|
import { deriveSessionStatus } from './features/sessions/session-status.js';
|
|
67
67
|
import { AUTOPILOT_CONTINUATION_PROMPT, AUTOPILOT_EXECUTOR_CONTINUATION_PROMPT, autopilotExecutorLaunchPrompt, defaultAutopilotPolicy, } from './features/autopilot/application/policy.js';
|
|
68
68
|
import { createRelyingPartyConfig } from './config.js';
|
|
69
|
-
import { parseOrgPlanAttention, toOrgPlanAttentionAcknowledgement, } from '../shared/contracts/org-plan-attention.js';
|
|
69
|
+
import { parseOrgPlanAttention, parseOrgPlanAttentionToolResponse, toOrgPlanAttentionAcknowledgement, } from '../shared/contracts/org-plan-attention.js';
|
|
70
70
|
import { autopilotWaitLeaseToolResponse, } from '../shared/contracts/autopilot-wait-lease.js';
|
|
71
71
|
import { agentCapacityRecoveryToolResponse } from '../shared/contracts/agent-capacity-recovery.js';
|
|
72
72
|
import { pwaIconUrl } from './features/pwa/register-routes.js';
|
|
@@ -137,7 +137,14 @@ export async function composeRelayApp(options) {
|
|
|
137
137
|
};
|
|
138
138
|
options.onAttentionTransitions?.(attentionTransitions);
|
|
139
139
|
const activity = new AgentActivityRegistry((snapshot, occurredAt) => {
|
|
140
|
-
events.publish(journal.append(snapshot.sessionId, 'agent.activity.updated',
|
|
140
|
+
events.publish(journal.append(snapshot.sessionId, 'agent.activity.updated', {
|
|
141
|
+
...toAgentActivityDto(snapshot),
|
|
142
|
+
...(autopilotStore.find(snapshot.sessionId)?.checkpoints?.activeHandoffId
|
|
143
|
+
? {
|
|
144
|
+
traceId: autopilotStore.find(snapshot.sessionId).checkpoints.activeHandoffId,
|
|
145
|
+
}
|
|
146
|
+
: {}),
|
|
147
|
+
}, occurredAt));
|
|
141
148
|
publishSessionStatus(snapshot.sessionId, occurredAt);
|
|
142
149
|
notifyAutopilotActivity(snapshot.sessionId);
|
|
143
150
|
}, {
|
|
@@ -400,8 +407,6 @@ export async function composeRelayApp(options) {
|
|
|
400
407
|
explicit: session.effectiveSkillSelection.skills,
|
|
401
408
|
}).skillsConfig;
|
|
402
409
|
const project = await skillProfiles.readWorkspaceDefault(session.workspacePath);
|
|
403
|
-
if (!options.explicitSkillProfile && !project)
|
|
404
|
-
return undefined;
|
|
405
410
|
return compileSkillOverride({
|
|
406
411
|
discovered: catalog.skills,
|
|
407
412
|
explicit: options.explicitSkillProfile?.skills,
|
|
@@ -498,7 +503,13 @@ export async function composeRelayApp(options) {
|
|
|
498
503
|
if (!interactions.resolve(sessionId, requestId, occurredAt, 'failed'))
|
|
499
504
|
return false;
|
|
500
505
|
autopilot.checkpointHandoffFailed(sessionId, turnId);
|
|
501
|
-
events.publish(journal.append(sessionId, 'org-plan.checkpoint-handoff-failed', {
|
|
506
|
+
events.publish(journal.append(sessionId, 'org-plan.checkpoint-handoff-failed', {
|
|
507
|
+
requestId,
|
|
508
|
+
reason: 'checkpointHandoffFailed',
|
|
509
|
+
...(autopilotStore.find(sessionId)?.checkpoints?.activeHandoffId
|
|
510
|
+
? { traceId: autopilotStore.find(sessionId).checkpoints.activeHandoffId }
|
|
511
|
+
: {}),
|
|
512
|
+
}, occurredAt));
|
|
502
513
|
publishAttentionSettlement(sessionId, requestId, occurredAt, 'failed');
|
|
503
514
|
// Neither this timeout nor a failed transport may schedule continuation.
|
|
504
515
|
// If a successful response is already blocked in the writable stream, drop
|
|
@@ -698,9 +709,16 @@ export async function composeRelayApp(options) {
|
|
|
698
709
|
const rootCommandId = resolvedOrigin.kind === 'root' ? completedCommandId(notification) : null;
|
|
699
710
|
if (rootCommandId)
|
|
700
711
|
autopilot.rootProcessCompleted(sessionId, rootCommandId);
|
|
712
|
+
const refreshChildTopology = notification.method === 'item/completed' &&
|
|
713
|
+
activityFacts.some((fact) => fact.kind === 'collaboration' &&
|
|
714
|
+
(fact.collaborationAction === 'spawn_agent' ||
|
|
715
|
+
fact.collaborationAction === 'resume_agent' ||
|
|
716
|
+
fact.collaborationAction === 'close_agent'));
|
|
701
717
|
if (!normalized) {
|
|
702
718
|
for (const activityFact of activityFacts)
|
|
703
719
|
activity.observe(activityFact);
|
|
720
|
+
if (refreshChildTopology)
|
|
721
|
+
void activity.refresh(sessionId);
|
|
704
722
|
return;
|
|
705
723
|
}
|
|
706
724
|
let completedSession;
|
|
@@ -719,6 +737,19 @@ export async function composeRelayApp(options) {
|
|
|
719
737
|
// activeTurnId while the completion fact makes the root appear idle.
|
|
720
738
|
for (const activityFact of activityFacts)
|
|
721
739
|
activity.observe(activityFact);
|
|
740
|
+
// Re-evaluate once both halves of the safe continuation boundary are
|
|
741
|
+
// observable. turnCompleted() releases the durable checkpoint before
|
|
742
|
+
// these facts project the idle root; its first evaluation can therefore
|
|
743
|
+
// still see the pre-completion activity snapshot. This second evaluation
|
|
744
|
+
// is idempotently fenced by the durable control row.
|
|
745
|
+
if (completedSession)
|
|
746
|
+
autopilot.evaluate(sessionId);
|
|
747
|
+
// Lifecycle notifications are fast transition evidence, while
|
|
748
|
+
// thread/list is the bounded topology authority. Reconcile after the
|
|
749
|
+
// mutation so canonical identity and every retained child reach both
|
|
750
|
+
// the GUI and Autopilot even when the notification is sparse.
|
|
751
|
+
if (refreshChildTopology)
|
|
752
|
+
void activity.refresh(sessionId);
|
|
722
753
|
events.publish(journal.append(sessionId, normalized.type, normalized.payload, normalized.occurredAt));
|
|
723
754
|
if (completedSession)
|
|
724
755
|
events.publish(journal.append(sessionId, 'session.updated', completedSession, occurredAt));
|
|
@@ -1433,6 +1464,10 @@ export async function composeRelayApp(options) {
|
|
|
1433
1464
|
const interaction = interactions.find(sessionId, requestId);
|
|
1434
1465
|
if (!interaction || interaction.kind !== 'orgPlanAttention')
|
|
1435
1466
|
return { kind: 'noActive' };
|
|
1467
|
+
const decision = parseOrgPlanAttentionToolResponse(response);
|
|
1468
|
+
const attention = parseOrgPlanAttention(interaction.payload);
|
|
1469
|
+
if (!decision || !attention)
|
|
1470
|
+
return { kind: 'staleOperation' };
|
|
1436
1471
|
const claim = interactions.claimOperation(sessionId, requestId, operationKey);
|
|
1437
1472
|
if (claim === 'resolved') {
|
|
1438
1473
|
const resolved = interactions.resolved(sessionId, requestId);
|
|
@@ -1448,12 +1483,23 @@ export async function composeRelayApp(options) {
|
|
|
1448
1483
|
if (acknowledged) {
|
|
1449
1484
|
if (!interactions.beginDelivery(sessionId, requestId, operationKey))
|
|
1450
1485
|
return { kind: 'staleOperation' };
|
|
1486
|
+
if (decision.action === 'resume' && attention.executorReplacement) {
|
|
1487
|
+
const authorization = autopilot.authorizeExecutorReplacement(sessionId, attention.executorReplacement.canonicalTaskName);
|
|
1488
|
+
if (!authorization.accepted) {
|
|
1489
|
+
interactions.retryDelivery(sessionId, requestId, operationKey);
|
|
1490
|
+
return { kind: 'replacementRejected' };
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1451
1493
|
const resolvedAt = new Date().toISOString();
|
|
1452
1494
|
if (!interactions.settleOperation(sessionId, requestId, operationKey, resolvedAt, 'answered'))
|
|
1453
1495
|
return { kind: 'staleOperation' };
|
|
1454
1496
|
const accepted = { kind: 'accepted', resolvedAt };
|
|
1455
1497
|
idempotency.put(scope, operationKey, 202, JSON.stringify({ kind: 'replayed', resolvedAt }));
|
|
1456
1498
|
publishAttentionSettlement(sessionId, requestId, resolvedAt, 'answered');
|
|
1499
|
+
if (decision.action === 'disableAutopilot')
|
|
1500
|
+
autopilot.disable(sessionId);
|
|
1501
|
+
else
|
|
1502
|
+
autopilot.enable(sessionId);
|
|
1457
1503
|
return accepted;
|
|
1458
1504
|
}
|
|
1459
1505
|
// A durable capability belongs to the session/thread, not the
|
|
@@ -1465,6 +1511,13 @@ export async function composeRelayApp(options) {
|
|
|
1465
1511
|
return { kind: 'writerUnavailable' };
|
|
1466
1512
|
if (!interactions.beginDelivery(sessionId, requestId, operationKey))
|
|
1467
1513
|
return { kind: 'staleOperation' };
|
|
1514
|
+
if (decision.action === 'resume' && attention.executorReplacement) {
|
|
1515
|
+
const authorization = autopilot.authorizeExecutorReplacement(sessionId, attention.executorReplacement.canonicalTaskName);
|
|
1516
|
+
if (!authorization.accepted) {
|
|
1517
|
+
interactions.retryDelivery(sessionId, requestId, operationKey);
|
|
1518
|
+
return { kind: 'replacementRejected' };
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1468
1521
|
const writer = runtime.attentionWriterState(sessionId, requestId);
|
|
1469
1522
|
if (writer === 'unavailable') {
|
|
1470
1523
|
interactions.retryDelivery(sessionId, requestId, operationKey);
|
|
@@ -1488,6 +1541,10 @@ export async function composeRelayApp(options) {
|
|
|
1488
1541
|
const accepted = { kind: 'accepted', resolvedAt };
|
|
1489
1542
|
idempotency.put(scope, operationKey, 202, JSON.stringify({ kind: 'replayed', resolvedAt }));
|
|
1490
1543
|
publishAttentionSettlement(sessionId, requestId, resolvedAt, 'answered');
|
|
1544
|
+
if (decision.action === 'disableAutopilot')
|
|
1545
|
+
autopilot.disable(sessionId);
|
|
1546
|
+
else
|
|
1547
|
+
autopilot.enable(sessionId);
|
|
1491
1548
|
return accepted;
|
|
1492
1549
|
})();
|
|
1493
1550
|
attentionResolutionOperations.set(key, operation);
|
|
@@ -196,6 +196,14 @@ function childOutcome(status, action, previous) {
|
|
|
196
196
|
return 'cancelled';
|
|
197
197
|
if (status === 'completed' || status === 'idle')
|
|
198
198
|
return 'partial';
|
|
199
|
+
// A follow-up reopens the same physical child. Do not keep presenting its
|
|
200
|
+
// previous boundary completion while its new turn is running.
|
|
201
|
+
if (action === 'resume_agent' ||
|
|
202
|
+
status === 'working' ||
|
|
203
|
+
status === 'active' ||
|
|
204
|
+
status === 'running' ||
|
|
205
|
+
status === 'pendingInit')
|
|
206
|
+
return undefined;
|
|
199
207
|
return previous;
|
|
200
208
|
}
|
|
201
209
|
function childState(status, action, previous = 'working') {
|
|
@@ -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,
|