gestalt-mobile 0.30.3 → 0.32.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-CcWoGWyP.css +1 -0
- package/dist/client/assets/index-CxAG4Fmv.js +37 -0
- package/dist/client/index.html +2 -2
- package/dist/server/server/app.js +2 -0
- package/dist/server/server/composition.contracts.js +63 -1
- package/dist/server/server/composition.js +32 -4
- 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/maintenance/application/ports.js +6 -0
- package/dist/server/server/features/maintenance/register-routes.js +10 -0
- package/dist/server/server/features/maintenance/update-restart/endpoint.js +20 -0
- 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/dist/server/server/platform/process/gestalt-update-restart.js +26 -0
- package/gestalt-supervision-capabilities.json +15 -0
- package/package.json +3 -2
- package/dist/client/assets/index-BK3qsn4l.js +0 -35
- package/dist/client/assets/index-DHqGG2Sf.css +0 -1
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-CxAG4Fmv.js"></script>
|
|
20
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CcWoGWyP.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
23
23
|
<div id="app"></div>
|
|
@@ -18,6 +18,7 @@ import { registerSkillRoutes } from './features/skills/register-routes.js';
|
|
|
18
18
|
import { registerOrgPlanAttentionRoutes } from './features/org-plan-attention/register-routes.js';
|
|
19
19
|
import { registerAutopilotRoutes } from './features/autopilot/register-routes.js';
|
|
20
20
|
import { registerFileRoutes } from './features/files/register-routes.js';
|
|
21
|
+
import { registerMaintenanceRoutes } from './features/maintenance/register-routes.js';
|
|
21
22
|
export async function buildApp(deps) {
|
|
22
23
|
// Keep the default boundary finite even when an endpoint forgot a narrower schema.
|
|
23
24
|
const app = fastify({ logger: false, bodyLimit: 1024 * 1024 });
|
|
@@ -52,6 +53,7 @@ export async function buildApp(deps) {
|
|
|
52
53
|
registerFileRoutes(app, deps);
|
|
53
54
|
registerGitRoutes(app, deps);
|
|
54
55
|
registerSkillRoutes(app, deps);
|
|
56
|
+
registerMaintenanceRoutes(app, deps);
|
|
55
57
|
registerProblemHandler(app, Boolean(deps.staticDir));
|
|
56
58
|
return app;
|
|
57
59
|
}
|
|
@@ -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-'));
|
|
@@ -1699,6 +1759,7 @@ describe('production composition', () => {
|
|
|
1699
1759
|
'protected',
|
|
1700
1760
|
],
|
|
1701
1761
|
['POST', '/api/git/clone', '/api/git/clone', 'protected'],
|
|
1762
|
+
['POST', '/api/maintenance/update-restart', '/api/maintenance/update-restart', 'protected'],
|
|
1702
1763
|
['GET', '/api/skills', '/api/skills', 'protected'],
|
|
1703
1764
|
['HEAD', '/api/skills', '/api/skills', 'protected'],
|
|
1704
1765
|
['GET', '/api/skill-profiles', '/api/skill-profiles', 'protected'],
|
|
@@ -2342,6 +2403,7 @@ describe('production composition', () => {
|
|
|
2342
2403
|
agentActivity: { root: { state: 'working' } },
|
|
2343
2404
|
});
|
|
2344
2405
|
expect(activityEvents.filter((message) => message.event.type === 'agent.activity.updated')).toHaveLength(initialActivityEvents + 1);
|
|
2406
|
+
expect(JSON.stringify(activityEvents)).not.toMatch(/ownerThreadId|ownerTaskPath|itemId|cpuPercent|rssBytes|resultArtifact/);
|
|
2345
2407
|
const activitySequence = activityEvents.find((message) => message.event.type === 'agent.activity.updated').event.sequence;
|
|
2346
2408
|
socket.close();
|
|
2347
2409
|
const replaySocket = new WebSocket(`ws://127.0.0.1:${address.port}/api/sessions/${sessionId}/events?after=${activitySequence - 1}`, {
|
|
@@ -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,9 +52,11 @@ 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';
|
|
59
|
+
import { GestaltUpdateRestartScheduler } from './platform/process/gestalt-update-restart.js';
|
|
57
60
|
import { autopilotWaitLeaseToolResponse, } from '../shared/contracts/autopilot-wait-lease.js';
|
|
58
61
|
const generatedProtocolVersion = 'codex-cli 0.144.3';
|
|
59
62
|
export async function composeRelayApp(options) {
|
|
@@ -95,6 +98,7 @@ export async function composeRelayApp(options) {
|
|
|
95
98
|
const withPendingInteractions = (session) => (session ? { ...session, pendingInteractions: interactions.list(session.id) } : null);
|
|
96
99
|
const events = new SessionEventBus();
|
|
97
100
|
let notifyAutopilotActivity = () => undefined;
|
|
101
|
+
let publishSessionStatus = () => undefined;
|
|
98
102
|
const attentionTransitions = {
|
|
99
103
|
subscribe: (sessionId, listener) => events.subscribe(sessionId, (event) => {
|
|
100
104
|
if (event.type !== 'org-plan.attention-required' &&
|
|
@@ -116,7 +120,8 @@ export async function composeRelayApp(options) {
|
|
|
116
120
|
};
|
|
117
121
|
options.onAttentionTransitions?.(attentionTransitions);
|
|
118
122
|
const activity = new AgentActivityRegistry((snapshot, occurredAt) => {
|
|
119
|
-
events.publish(journal.append(snapshot.sessionId, 'agent.activity.updated', snapshot, occurredAt));
|
|
123
|
+
events.publish(journal.append(snapshot.sessionId, 'agent.activity.updated', toAgentActivityDto(snapshot), occurredAt));
|
|
124
|
+
publishSessionStatus(snapshot.sessionId, occurredAt);
|
|
120
125
|
notifyAutopilotActivity(snapshot.sessionId);
|
|
121
126
|
}, {
|
|
122
127
|
// Evidence arms one bounded reconciliation; healthy sessions are never polled.
|
|
@@ -287,6 +292,19 @@ export async function composeRelayApp(options) {
|
|
|
287
292
|
},
|
|
288
293
|
});
|
|
289
294
|
notifyAutopilotActivity = (sessionId) => autopilot.activityChanged(sessionId);
|
|
295
|
+
publishSessionStatus = (sessionId, occurredAt) => {
|
|
296
|
+
const session = sessions.find(sessionId);
|
|
297
|
+
if (!session)
|
|
298
|
+
return;
|
|
299
|
+
events.publish(journal.append(sessionId, 'session.status.updated', deriveSessionStatus({
|
|
300
|
+
session,
|
|
301
|
+
plan: supervisedPlans.find(sessionId),
|
|
302
|
+
activity: activity.snapshot(sessionId, occurredAt),
|
|
303
|
+
autopilot: autopilot.snapshot(sessionId),
|
|
304
|
+
pendingAttention: interactions.list(sessionId).length > 0,
|
|
305
|
+
observedAt: occurredAt,
|
|
306
|
+
}), occurredAt));
|
|
307
|
+
};
|
|
290
308
|
options.onAutopilotCoordinator?.(autopilot);
|
|
291
309
|
const workspaces = new FilesystemWorkspaceCatalog(root);
|
|
292
310
|
const workspaceFiles = new FilesystemWorkspaceFiles();
|
|
@@ -337,6 +355,7 @@ export async function composeRelayApp(options) {
|
|
|
337
355
|
if (interaction?.kind === 'orgPlanAttention')
|
|
338
356
|
events.publish(journal.append(sessionId, 'org-plan.attention-resolved', { requestId, turnId: interaction.turnId ?? null, resolvedAt: occurredAt, outcome }, occurredAt));
|
|
339
357
|
autopilot.semanticEvent(sessionId, 'interactionChanged');
|
|
358
|
+
publishSessionStatus(sessionId, occurredAt);
|
|
340
359
|
};
|
|
341
360
|
const publishAttentionSettlement = (sessionId, requestId, occurredAt, outcome) => {
|
|
342
361
|
const remaining = interactions.list(sessionId);
|
|
@@ -385,12 +404,12 @@ export async function composeRelayApp(options) {
|
|
|
385
404
|
if (update.kind === 'updated' && update.reason === 'supervision-start')
|
|
386
405
|
autopilot.supervisionStarted(sessionId);
|
|
387
406
|
autopilot.planStatusChanged(sessionId);
|
|
407
|
+
publishSessionStatus(sessionId, new Date().toISOString());
|
|
388
408
|
};
|
|
389
409
|
runtime = options.startAppServers
|
|
390
410
|
? new CodexSessionRuntime(options.launchAppServer ?? launchCodexAppServer, undefined, (sessionId, notification, origin) => {
|
|
391
411
|
const occurredAt = new Date().toISOString();
|
|
392
|
-
|
|
393
|
-
activity.observe(activityFact);
|
|
412
|
+
const activityFacts = decodeAgentActivityFacts(sessionId, occurredAt, notification);
|
|
394
413
|
const resolvedRequestId = resolvedServerRequestId(notification);
|
|
395
414
|
if (resolvedRequestId) {
|
|
396
415
|
const interaction = interactions.find(sessionId, resolvedRequestId);
|
|
@@ -416,8 +435,11 @@ export async function composeRelayApp(options) {
|
|
|
416
435
|
? { ...origin, kind: 'root' }
|
|
417
436
|
: origin;
|
|
418
437
|
const normalized = normalizeCodexNotification(sessionId, 0, occurredAt, notification, currentSession?.workspacePath, currentSession?.activeTurnId, resolvedOrigin);
|
|
419
|
-
if (!normalized)
|
|
438
|
+
if (!normalized) {
|
|
439
|
+
for (const activityFact of activityFacts)
|
|
440
|
+
activity.observe(activityFact);
|
|
420
441
|
return;
|
|
442
|
+
}
|
|
421
443
|
let completedSession;
|
|
422
444
|
if (normalized.type === 'turnCompleted') {
|
|
423
445
|
const turnId = normalized.payload.turn?.id;
|
|
@@ -429,6 +451,11 @@ export async function composeRelayApp(options) {
|
|
|
429
451
|
}
|
|
430
452
|
planMeasurementRefresh?.refreshNow(sessionId);
|
|
431
453
|
}
|
|
454
|
+
// Activity publication also derives and broadcasts the session verdict.
|
|
455
|
+
// Complete the durable turn first so its status cannot retain the just-finished
|
|
456
|
+
// activeTurnId while the completion fact makes the root appear idle.
|
|
457
|
+
for (const activityFact of activityFacts)
|
|
458
|
+
activity.observe(activityFact);
|
|
432
459
|
events.publish(journal.append(sessionId, normalized.type, normalized.payload, normalized.occurredAt));
|
|
433
460
|
if (completedSession)
|
|
434
461
|
events.publish(journal.append(sessionId, 'session.updated', completedSession, occurredAt));
|
|
@@ -616,6 +643,7 @@ export async function composeRelayApp(options) {
|
|
|
616
643
|
};
|
|
617
644
|
},
|
|
618
645
|
},
|
|
646
|
+
maintenance: { updateRestart: new GestaltUpdateRestartScheduler() },
|
|
619
647
|
skills: {
|
|
620
648
|
workspaces,
|
|
621
649
|
profiles: options.profiles,
|
|
@@ -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
|
})),
|