gestalt-mobile 0.30.2 → 0.31.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/dist/client/assets/index-DDwVphGb.css +1 -0
- package/dist/client/assets/index-DKzUE8x1.js +36 -0
- package/dist/client/index.html +2 -2
- package/dist/server/server/composition.contracts.js +92 -1
- package/dist/server/server/composition.js +36 -5
- package/dist/server/server/features/agent-activity/activity-dto.js +35 -0
- package/dist/server/server/features/agent-activity/model.js +21 -4
- package/dist/server/server/features/autopilot/application/service.js +172 -24
- package/dist/server/server/features/autopilot/domain/autopilot-session.js +100 -1
- package/dist/server/server/features/sessions/get-session/endpoint.js +23 -9
- package/dist/server/server/features/sessions/list-sessions/endpoint.js +14 -2
- package/dist/server/server/features/sessions/register-routes.js +1 -1
- package/dist/server/server/features/sessions/session-status.js +55 -0
- package/dist/server/server/platform/codex/session-runtime.js +15 -4
- package/gestalt-supervision-capabilities.json +15 -0
- package/package.json +3 -2
- package/dist/client/assets/index-CKhqx71S.css +0 -1
- package/dist/client/assets/index-v8UC90bI.js +0 -34
package/dist/client/index.html
CHANGED
|
@@ -16,8 +16,8 @@ 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-
|
|
20
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
19
|
+
<script type="module" crossorigin src="/assets/index-DKzUE8x1.js"></script>
|
|
20
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DDwVphGb.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
23
23
|
<div id="app"></div>
|
|
@@ -690,7 +690,9 @@ describe('production composition', () => {
|
|
|
690
690
|
url: `/api/sessions/${fixture.sessionId}/autopilot`,
|
|
691
691
|
payload: { enabled: true },
|
|
692
692
|
})).statusCode).toBe(200);
|
|
693
|
-
|
|
693
|
+
// Initial supervision schedules its control and keeps a separate,
|
|
694
|
+
// bounded reconciliation watchdog while activity is still stale.
|
|
695
|
+
await vi.waitFor(() => expect(timers).toHaveLength(2));
|
|
694
696
|
const staleTimer = timers[0];
|
|
695
697
|
if (action === 'close') {
|
|
696
698
|
await writeFile(join(fixture.workspacePath, 'autopilot.org'), completedAutopilotPlanText());
|
|
@@ -1366,6 +1368,64 @@ describe('production composition', () => {
|
|
|
1366
1368
|
});
|
|
1367
1369
|
});
|
|
1368
1370
|
describeCompositionConcern('lifecycle', () => {
|
|
1371
|
+
it('publishes an idle websocket status after saving a completed turn', async () => {
|
|
1372
|
+
const root = await mkdtemp(join(tmpdir(), 'gestalt-mobile-root-'));
|
|
1373
|
+
const dataDir = await mkdtemp(join(tmpdir(), 'gestalt-mobile-state-'));
|
|
1374
|
+
ownTemporaryPaths(root, dataDir);
|
|
1375
|
+
await mkdir(join(root, 'workspace'));
|
|
1376
|
+
const handles = [];
|
|
1377
|
+
const app = await composeAuthorizedApp({
|
|
1378
|
+
root,
|
|
1379
|
+
dataDir,
|
|
1380
|
+
relyingParty,
|
|
1381
|
+
installedCodexVersion: 'codex-cli 0.144.3',
|
|
1382
|
+
startAppServers: true,
|
|
1383
|
+
launchAppServer: liveAppServer(handles),
|
|
1384
|
+
profiles: {
|
|
1385
|
+
list: async () => [],
|
|
1386
|
+
require: async () => ({
|
|
1387
|
+
name: 'default',
|
|
1388
|
+
state: 'ok',
|
|
1389
|
+
status: 'ready',
|
|
1390
|
+
}),
|
|
1391
|
+
},
|
|
1392
|
+
});
|
|
1393
|
+
const sessionId = await createComposedSession(app);
|
|
1394
|
+
const handle = handles.find((candidate) => candidate.notify);
|
|
1395
|
+
expect(handle?.notify).toBeDefined();
|
|
1396
|
+
await app.listen({ host: '127.0.0.1', port: 0 });
|
|
1397
|
+
const address = app.server.address();
|
|
1398
|
+
if (!address || typeof address === 'string')
|
|
1399
|
+
throw new Error('Expected TCP listener');
|
|
1400
|
+
const socket = new WebSocket(`ws://127.0.0.1:${address.port}/api/sessions/${sessionId}/events?after=0`, {
|
|
1401
|
+
headers: {
|
|
1402
|
+
origin: relyingParty.publicOrigin,
|
|
1403
|
+
cookie: 'gestalt_mobile_session=test-session',
|
|
1404
|
+
},
|
|
1405
|
+
});
|
|
1406
|
+
const messages = [];
|
|
1407
|
+
socket.on('message', (data) => messages.push(JSON.parse(String(data))));
|
|
1408
|
+
await once(socket, 'open');
|
|
1409
|
+
const started = await app.inject({
|
|
1410
|
+
method: 'POST',
|
|
1411
|
+
url: `/api/sessions/${sessionId}/turns`,
|
|
1412
|
+
payload: { text: 'complete this turn' },
|
|
1413
|
+
});
|
|
1414
|
+
expect(started.statusCode).toBe(202);
|
|
1415
|
+
const turnId = started.json().activeTurnId;
|
|
1416
|
+
handle.notify({ method: 'turn/completed', params: { turn: { id: turnId } } });
|
|
1417
|
+
await vi.waitFor(() => {
|
|
1418
|
+
const status = messages
|
|
1419
|
+
.filter((message) => message.event.type === 'session.status.updated')
|
|
1420
|
+
.at(-1)?.event.payload;
|
|
1421
|
+
expect(status).toMatchObject({ state: 'idle' });
|
|
1422
|
+
});
|
|
1423
|
+
expect((await app.inject(`/api/sessions/${sessionId}`)).json()).toMatchObject({
|
|
1424
|
+
activeTurnId: null,
|
|
1425
|
+
});
|
|
1426
|
+
socket.close();
|
|
1427
|
+
await app.close();
|
|
1428
|
+
});
|
|
1369
1429
|
it('forgets a session while activity reconciliation is pending without publishing late activity', async () => {
|
|
1370
1430
|
const root = await mkdtemp(join(tmpdir(), 'gestalt-mobile-root-'));
|
|
1371
1431
|
const dataDir = await mkdtemp(join(tmpdir(), 'gestalt-mobile-state-'));
|
|
@@ -2342,6 +2402,7 @@ describe('production composition', () => {
|
|
|
2342
2402
|
agentActivity: { root: { state: 'working' } },
|
|
2343
2403
|
});
|
|
2344
2404
|
expect(activityEvents.filter((message) => message.event.type === 'agent.activity.updated')).toHaveLength(initialActivityEvents + 1);
|
|
2405
|
+
expect(JSON.stringify(activityEvents)).not.toMatch(/ownerThreadId|ownerTaskPath|itemId|cpuPercent|rssBytes|resultArtifact/);
|
|
2345
2406
|
const activitySequence = activityEvents.find((message) => message.event.type === 'agent.activity.updated').event.sequence;
|
|
2346
2407
|
socket.close();
|
|
2347
2408
|
const replaySocket = new WebSocket(`ws://127.0.0.1:${address.port}/api/sessions/${sessionId}/events?after=${activitySequence - 1}`, {
|
|
@@ -2500,6 +2561,36 @@ describe('production composition', () => {
|
|
|
2500
2561
|
expect((await app.inject(`/api/sessions/${sessionId}/history`)).json().interactions).toEqual(expect.arrayContaining([
|
|
2501
2562
|
expect.objectContaining({ requestId: '9', kind: 'orgPlanAttention', outcome: 'failed' }),
|
|
2502
2563
|
]));
|
|
2564
|
+
const quiz = handle.request({
|
|
2565
|
+
id: 71,
|
|
2566
|
+
method: 'item/tool/call',
|
|
2567
|
+
params: {
|
|
2568
|
+
tool: 'gestalt_quiz',
|
|
2569
|
+
arguments: {
|
|
2570
|
+
questions: [
|
|
2571
|
+
{
|
|
2572
|
+
id: 'mode',
|
|
2573
|
+
header: 'Mode',
|
|
2574
|
+
question: 'How should this run?',
|
|
2575
|
+
choices: [
|
|
2576
|
+
{ label: 'Solo', description: 'Use one agent.' },
|
|
2577
|
+
{ label: 'Team', description: 'Use several agents.' },
|
|
2578
|
+
],
|
|
2579
|
+
allowCustom: false,
|
|
2580
|
+
},
|
|
2581
|
+
],
|
|
2582
|
+
},
|
|
2583
|
+
},
|
|
2584
|
+
});
|
|
2585
|
+
const clearedQuiz = quiz.catch((error) => error);
|
|
2586
|
+
handle.notify({
|
|
2587
|
+
method: 'serverRequest/resolved',
|
|
2588
|
+
params: { threadId: 'thread-1', requestId: 71 },
|
|
2589
|
+
});
|
|
2590
|
+
expect(await clearedQuiz).toEqual(expect.objectContaining({ message: 'CODEX_SERVER_REQUEST_CLEARED' }));
|
|
2591
|
+
expect((await app.inject(`/api/sessions/${sessionId}/history`)).json().interactions).toEqual(expect.arrayContaining([
|
|
2592
|
+
expect.objectContaining({ requestId: '71', kind: 'quiz', resolvedAt: null }),
|
|
2593
|
+
]));
|
|
2503
2594
|
await app.close();
|
|
2504
2595
|
});
|
|
2505
2596
|
});
|
|
@@ -34,6 +34,7 @@ import { SessionSupervisor } from './platform/runtime/session-supervisor.js';
|
|
|
34
34
|
import { mapWithConcurrency } from './platform/runtime/concurrency.js';
|
|
35
35
|
import { RelaySession, } from './features/sessions/model/relay-session.js';
|
|
36
36
|
import { AgentActivityRegistry } from './features/agent-activity/registry.js';
|
|
37
|
+
import { toAgentActivityDto } from './features/agent-activity/activity-dto.js';
|
|
37
38
|
import { decodeAgentActivityFacts } from './platform/codex/activity-facts.js';
|
|
38
39
|
import { isAutopilotWaitLeaseCall, resolvedServerRequestId, toPendingInteraction, } from './platform/codex/server-request.js';
|
|
39
40
|
import { parseOrgPlanCheckpoint, toOrgPlanCheckpointToolResponse, } from '../shared/contracts/org-plan-checkpoint.js';
|
|
@@ -51,6 +52,7 @@ import { checkpointPlanMeasurement } from './platform/plans/plan-measurement-com
|
|
|
51
52
|
import { PlanMeasurementRefresh } from './platform/plans/plan-measurement-refresh.js';
|
|
52
53
|
import { SqliteAutopilotStore } from './platform/persistence/sqlite-autopilot-store.js';
|
|
53
54
|
import { AutopilotCoordinator } from './features/autopilot/application/service.js';
|
|
55
|
+
import { deriveSessionStatus } from './features/sessions/session-status.js';
|
|
54
56
|
import { AUTOPILOT_CONTINUATION_PROMPT, AUTOPILOT_EXECUTOR_CONTINUATION_PROMPT, autopilotExecutorLaunchPrompt, defaultAutopilotPolicy, } from './features/autopilot/application/policy.js';
|
|
55
57
|
import { createRelyingPartyConfig } from './config.js';
|
|
56
58
|
import { parseOrgPlanAttention } from '../shared/contracts/org-plan-attention.js';
|
|
@@ -95,6 +97,7 @@ export async function composeRelayApp(options) {
|
|
|
95
97
|
const withPendingInteractions = (session) => (session ? { ...session, pendingInteractions: interactions.list(session.id) } : null);
|
|
96
98
|
const events = new SessionEventBus();
|
|
97
99
|
let notifyAutopilotActivity = () => undefined;
|
|
100
|
+
let publishSessionStatus = () => undefined;
|
|
98
101
|
const attentionTransitions = {
|
|
99
102
|
subscribe: (sessionId, listener) => events.subscribe(sessionId, (event) => {
|
|
100
103
|
if (event.type !== 'org-plan.attention-required' &&
|
|
@@ -116,7 +119,8 @@ export async function composeRelayApp(options) {
|
|
|
116
119
|
};
|
|
117
120
|
options.onAttentionTransitions?.(attentionTransitions);
|
|
118
121
|
const activity = new AgentActivityRegistry((snapshot, occurredAt) => {
|
|
119
|
-
events.publish(journal.append(snapshot.sessionId, 'agent.activity.updated', snapshot, occurredAt));
|
|
122
|
+
events.publish(journal.append(snapshot.sessionId, 'agent.activity.updated', toAgentActivityDto(snapshot), occurredAt));
|
|
123
|
+
publishSessionStatus(snapshot.sessionId, occurredAt);
|
|
120
124
|
notifyAutopilotActivity(snapshot.sessionId);
|
|
121
125
|
}, {
|
|
122
126
|
// Evidence arms one bounded reconciliation; healthy sessions are never polled.
|
|
@@ -287,6 +291,19 @@ export async function composeRelayApp(options) {
|
|
|
287
291
|
},
|
|
288
292
|
});
|
|
289
293
|
notifyAutopilotActivity = (sessionId) => autopilot.activityChanged(sessionId);
|
|
294
|
+
publishSessionStatus = (sessionId, occurredAt) => {
|
|
295
|
+
const session = sessions.find(sessionId);
|
|
296
|
+
if (!session)
|
|
297
|
+
return;
|
|
298
|
+
events.publish(journal.append(sessionId, 'session.status.updated', deriveSessionStatus({
|
|
299
|
+
session,
|
|
300
|
+
plan: supervisedPlans.find(sessionId),
|
|
301
|
+
activity: activity.snapshot(sessionId, occurredAt),
|
|
302
|
+
autopilot: autopilot.snapshot(sessionId),
|
|
303
|
+
pendingAttention: interactions.list(sessionId).length > 0,
|
|
304
|
+
observedAt: occurredAt,
|
|
305
|
+
}), occurredAt));
|
|
306
|
+
};
|
|
290
307
|
options.onAutopilotCoordinator?.(autopilot);
|
|
291
308
|
const workspaces = new FilesystemWorkspaceCatalog(root);
|
|
292
309
|
const workspaceFiles = new FilesystemWorkspaceFiles();
|
|
@@ -337,6 +354,7 @@ export async function composeRelayApp(options) {
|
|
|
337
354
|
if (interaction?.kind === 'orgPlanAttention')
|
|
338
355
|
events.publish(journal.append(sessionId, 'org-plan.attention-resolved', { requestId, turnId: interaction.turnId ?? null, resolvedAt: occurredAt, outcome }, occurredAt));
|
|
339
356
|
autopilot.semanticEvent(sessionId, 'interactionChanged');
|
|
357
|
+
publishSessionStatus(sessionId, occurredAt);
|
|
340
358
|
};
|
|
341
359
|
const publishAttentionSettlement = (sessionId, requestId, occurredAt, outcome) => {
|
|
342
360
|
const remaining = interactions.list(sessionId);
|
|
@@ -352,6 +370,10 @@ export async function composeRelayApp(options) {
|
|
|
352
370
|
};
|
|
353
371
|
const dismissPendingInteractions = (sessionId, occurredAt, outcome = 'dismissed') => {
|
|
354
372
|
for (const interaction of interactions.list(sessionId)) {
|
|
373
|
+
// Quiz answers remain useful after Codex clears the original dynamic-tool
|
|
374
|
+
// request: the client can deliver them as a follow-up prompt instead.
|
|
375
|
+
if (interaction.kind === 'quiz')
|
|
376
|
+
continue;
|
|
355
377
|
if (interactions.resolve(sessionId, interaction.requestId, occurredAt, outcome))
|
|
356
378
|
publishInteractionResolved(sessionId, interaction.requestId, occurredAt, outcome);
|
|
357
379
|
}
|
|
@@ -381,17 +403,18 @@ export async function composeRelayApp(options) {
|
|
|
381
403
|
if (update.kind === 'updated' && update.reason === 'supervision-start')
|
|
382
404
|
autopilot.supervisionStarted(sessionId);
|
|
383
405
|
autopilot.planStatusChanged(sessionId);
|
|
406
|
+
publishSessionStatus(sessionId, new Date().toISOString());
|
|
384
407
|
};
|
|
385
408
|
runtime = options.startAppServers
|
|
386
409
|
? new CodexSessionRuntime(options.launchAppServer ?? launchCodexAppServer, undefined, (sessionId, notification, origin) => {
|
|
387
410
|
const occurredAt = new Date().toISOString();
|
|
388
|
-
|
|
389
|
-
activity.observe(activityFact);
|
|
411
|
+
const activityFacts = decodeAgentActivityFacts(sessionId, occurredAt, notification);
|
|
390
412
|
const resolvedRequestId = resolvedServerRequestId(notification);
|
|
391
413
|
if (resolvedRequestId) {
|
|
392
414
|
const interaction = interactions.find(sessionId, resolvedRequestId);
|
|
393
415
|
const outcome = interaction?.kind === 'orgPlanAttention' ? 'failed' : 'dismissed';
|
|
394
|
-
if (
|
|
416
|
+
if (interaction?.kind !== 'quiz' &&
|
|
417
|
+
interactions.resolve(sessionId, resolvedRequestId, occurredAt, outcome)) {
|
|
395
418
|
if (outcome === 'failed')
|
|
396
419
|
publishAttentionSettlement(sessionId, resolvedRequestId, occurredAt, outcome);
|
|
397
420
|
else
|
|
@@ -411,8 +434,11 @@ export async function composeRelayApp(options) {
|
|
|
411
434
|
? { ...origin, kind: 'root' }
|
|
412
435
|
: origin;
|
|
413
436
|
const normalized = normalizeCodexNotification(sessionId, 0, occurredAt, notification, currentSession?.workspacePath, currentSession?.activeTurnId, resolvedOrigin);
|
|
414
|
-
if (!normalized)
|
|
437
|
+
if (!normalized) {
|
|
438
|
+
for (const activityFact of activityFacts)
|
|
439
|
+
activity.observe(activityFact);
|
|
415
440
|
return;
|
|
441
|
+
}
|
|
416
442
|
let completedSession;
|
|
417
443
|
if (normalized.type === 'turnCompleted') {
|
|
418
444
|
const turnId = normalized.payload.turn?.id;
|
|
@@ -424,6 +450,11 @@ export async function composeRelayApp(options) {
|
|
|
424
450
|
}
|
|
425
451
|
planMeasurementRefresh?.refreshNow(sessionId);
|
|
426
452
|
}
|
|
453
|
+
// Activity publication also derives and broadcasts the session verdict.
|
|
454
|
+
// Complete the durable turn first so its status cannot retain the just-finished
|
|
455
|
+
// activeTurnId while the completion fact makes the root appear idle.
|
|
456
|
+
for (const activityFact of activityFacts)
|
|
457
|
+
activity.observe(activityFact);
|
|
427
458
|
events.publish(journal.append(sessionId, normalized.type, normalized.payload, normalized.occurredAt));
|
|
428
459
|
if (completedSession)
|
|
429
460
|
events.publish(journal.append(sessionId, 'session.updated', completedSession, occurredAt));
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
/** Public roster projection: never expose process, thread, task-path, or host metrics identifiers. */
|
|
7
|
+
export function toAgentActivityDto(snapshot) {
|
|
8
|
+
return {
|
|
9
|
+
sessionId: snapshot.sessionId,
|
|
10
|
+
root: snapshot.root,
|
|
11
|
+
subagents: snapshot.subagents.slice(0, 64).map((child) => ({
|
|
12
|
+
id: child.id,
|
|
13
|
+
...(child.nickname ? { nickname: child.nickname } : {}),
|
|
14
|
+
...(child.role ? { role: child.role } : {}),
|
|
15
|
+
...(child.model ? { model: child.model } : {}),
|
|
16
|
+
...(child.canonicalTaskName ? { canonicalTaskName: child.canonicalTaskName } : {}),
|
|
17
|
+
...(child.canonicalPosition ? { canonicalPosition: child.canonicalPosition } : {}),
|
|
18
|
+
...(child.continuationGeneration
|
|
19
|
+
? { continuationGeneration: child.continuationGeneration }
|
|
20
|
+
: {}),
|
|
21
|
+
...(child.outcome ? { outcome: child.outcome } : {}),
|
|
22
|
+
ownedProcesses: (child.ownedProcesses ?? []).slice(0, 64).map((process) => ({
|
|
23
|
+
state: process.state,
|
|
24
|
+
ownership: process.ownership,
|
|
25
|
+
observedAt: process.observedAt,
|
|
26
|
+
})),
|
|
27
|
+
state: child.state,
|
|
28
|
+
reason: child.reason,
|
|
29
|
+
observedAt: child.observedAt,
|
|
30
|
+
lastActivityAt: child.lastActivityAt,
|
|
31
|
+
})),
|
|
32
|
+
aggregateSubagents: snapshot.aggregateSubagents,
|
|
33
|
+
confidence: snapshot.confidence,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -84,6 +84,9 @@ export function projectAgentActivity(current, fact) {
|
|
|
84
84
|
const before = children.get(fact.childId);
|
|
85
85
|
const taskPath = fact.childTaskPath ?? before?.taskPath;
|
|
86
86
|
const identity = taskPath ? parseOrgPlanAgentIdentity(taskPath) : null;
|
|
87
|
+
const terminalReviewer = taskPath?.split('/').filter(Boolean).at(-1) === 'final_review' ||
|
|
88
|
+
before?.canonicalTaskName === 'final_review' ||
|
|
89
|
+
fact.childRole === 'org-plan-reviewer';
|
|
87
90
|
const outcome = childOutcome(fact.childStatus, fact.collaborationAction, before?.outcome);
|
|
88
91
|
const child = Object.freeze({
|
|
89
92
|
id: fact.childId,
|
|
@@ -110,7 +113,9 @@ export function projectAgentActivity(current, fact) {
|
|
|
110
113
|
canonicalPosition: identity.canonicalPosition,
|
|
111
114
|
continuationGeneration: identity.generation,
|
|
112
115
|
}
|
|
113
|
-
:
|
|
116
|
+
: terminalReviewer
|
|
117
|
+
? { canonicalTaskName: 'final_review' }
|
|
118
|
+
: {}),
|
|
114
119
|
...(outcome ? { outcome } : {}),
|
|
115
120
|
...(fact.childOwnedProcesses
|
|
116
121
|
? { ownedProcesses: Object.freeze([...fact.childOwnedProcesses]) }
|
|
@@ -143,14 +148,20 @@ export function projectAgentActivity(current, fact) {
|
|
|
143
148
|
export function withActivityConfidence(snapshot, confidence) {
|
|
144
149
|
return snapshot.confidence === confidence ? snapshot : Object.freeze({ ...snapshot, confidence });
|
|
145
150
|
}
|
|
146
|
-
/**
|
|
151
|
+
/**
|
|
152
|
+
* A process disconnect makes the roster stale, but is not evidence that its
|
|
153
|
+
* children ceased to exist. Keep their authoritative identity and present the
|
|
154
|
+
* last known physical generation as disconnected until disposal or a later
|
|
155
|
+
* reconciliation replaces it.
|
|
156
|
+
*/
|
|
147
157
|
export function clearAgentActivityChildren(snapshot) {
|
|
148
158
|
if (snapshot.subagents.length === 0 && snapshot.aggregateSubagents === 'idle')
|
|
149
159
|
return snapshot;
|
|
160
|
+
const subagents = Object.freeze(snapshot.subagents.map((child) => Object.freeze({ ...child, state: 'disconnected', reason: 'processExited' })));
|
|
150
161
|
return Object.freeze({
|
|
151
162
|
...snapshot,
|
|
152
|
-
subagents
|
|
153
|
-
aggregateSubagents:
|
|
163
|
+
subagents,
|
|
164
|
+
aggregateSubagents: aggregate(subagents),
|
|
154
165
|
});
|
|
155
166
|
}
|
|
156
167
|
function applyStatus(root, status) {
|
|
@@ -231,6 +242,12 @@ function semantic(snapshot) {
|
|
|
231
242
|
nickname: child.nickname,
|
|
232
243
|
role: child.role,
|
|
233
244
|
model: child.model,
|
|
245
|
+
taskPath: child.taskPath,
|
|
246
|
+
canonicalTaskName: child.canonicalTaskName,
|
|
247
|
+
canonicalPosition: child.canonicalPosition,
|
|
248
|
+
continuationGeneration: child.continuationGeneration,
|
|
249
|
+
outcome: child.outcome,
|
|
250
|
+
ownedProcesses: child.ownedProcesses,
|
|
234
251
|
state: child.state,
|
|
235
252
|
reason: child.reason,
|
|
236
253
|
})),
|