@dotdrelle/wiki-manager 0.11.10 → 0.12.1
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 +32 -7
- package/agents.docker-compose.yml +3 -0
- package/docker-compose.yml +1 -0
- package/package.json +3 -2
- package/src/activity/activityAggregator.js +109 -0
- package/src/activity/activityAggregator.test.js +60 -0
- package/src/activity/activityDeduplicator.js +50 -0
- package/src/activity/progressCalculator.js +61 -0
- package/src/activity/runSynthesis.js +15 -0
- package/src/agent/graph.js +101 -38
- package/src/agent/graph.test.js +75 -5
- package/src/cli/wiki-manager.js +22 -0
- package/src/contracts/schemas.js +249 -13
- package/src/contracts/schemas.test.js +147 -2
- package/src/core/activity.js +8 -5
- package/src/core/agentEvents.js +260 -23
- package/src/core/agentEvents.test.js +53 -0
- package/src/core/compose.js +6 -1
- package/src/core/dockerCompose.test.js +12 -0
- package/src/core/documentIntake.js +33 -38
- package/src/core/documentIntake.test.js +25 -0
- package/src/core/env.js +11 -1
- package/src/core/jobQueue.js +26 -2
- package/src/core/mcp.js +30 -3
- package/src/core/mcp.test.js +69 -1
- package/src/core/plan.js +46 -4
- package/src/core/plan.test.js +16 -1
- package/src/core/planPatch.js +64 -3
- package/src/core/planPatch.test.js +110 -1
- package/src/core/queueStore.test.js +21 -0
- package/src/core/runtimeLog.js +119 -0
- package/src/core/runtimeLog.test.js +84 -0
- package/src/core/wikirc.js +22 -0
- package/src/core/wikirc.test.js +49 -1
- package/src/core/workflow.js +14 -3
- package/src/graph/graphAggregator.js +5 -0
- package/src/graph/graphPatch.js +8 -0
- package/src/graph/graphSnapshot.js +14 -0
- package/src/graph/graphVisibilityPolicy.js +40 -0
- package/src/graph/runGraphProjector.js +87 -0
- package/src/graph/runGraphProjector.test.js +111 -0
- package/src/orchestrator/agentRegistry.js +172 -0
- package/src/orchestrator/agentRegistry.test.js +114 -0
- package/src/orchestrator/approvalPolicy.js +126 -0
- package/src/orchestrator/approvalPolicy.test.js +78 -0
- package/src/orchestrator/assignmentManager.js +80 -0
- package/src/orchestrator/attemptManager.js +168 -0
- package/src/orchestrator/attemptManager.test.js +160 -0
- package/src/orchestrator/budgetManager.js +127 -0
- package/src/orchestrator/capabilityRegistry.js +64 -0
- package/src/orchestrator/capabilityRegistry.test.js +59 -0
- package/src/orchestrator/capabilityResolver.js +151 -0
- package/src/orchestrator/capabilityResolver.test.js +127 -0
- package/src/orchestrator/dependencyResolver.js +112 -0
- package/src/orchestrator/dispatcher.js +302 -0
- package/src/orchestrator/lockManager.js +51 -0
- package/src/orchestrator/planIntegrator.js +268 -0
- package/src/orchestrator/planIntegrator.test.js +213 -0
- package/src/orchestrator/planValidator.js +534 -0
- package/src/orchestrator/planValidator.test.js +262 -0
- package/src/orchestrator/resultAggregator.js +248 -0
- package/src/orchestrator/resultAggregator.test.js +211 -0
- package/src/orchestrator/scheduler.js +103 -0
- package/src/orchestrator/scheduler.test.js +138 -0
- package/src/runtime/approvals.js +130 -1
- package/src/runtime/controlMessages.js +43 -0
- package/src/runtime/controlMessages.test.js +21 -0
- package/src/runtime/donna-contract.test.js +296 -33
- package/src/runtime/recoveryManager.js +176 -0
- package/src/runtime/recoveryManager.test.js +162 -0
- package/src/runtime/runner.e2e.test.js +98 -12
- package/src/runtime/runner.js +261 -227
- package/src/runtime/runner.test.js +249 -452
- package/src/runtime/server.js +142 -24
- package/src/runtime/server.test.js +192 -62
- package/src/runtime/store.js +837 -2
- package/src/runtime/store.test.js +269 -28
- package/src/runtime/supervisor.js +33 -1
- package/src/runtime/supervisor.test.js +47 -1
- package/src/shell/RightPane.tsx +8 -5
- package/src/shell/repl.js +7 -11
- package/src/shell/repl.test.js +26 -5
- package/src/shell/tui.tsx +1 -0
- package/src/shell/useAgent.ts +5 -1
- package/src/shell/useSession.ts +75 -19
package/src/shell/repl.test.js
CHANGED
|
@@ -59,6 +59,10 @@ test('applyRuntimeStateToShellSession projects runtime state into shell session'
|
|
|
59
59
|
|
|
60
60
|
assert.equal(applied, true);
|
|
61
61
|
assert.equal(session.agentProjection.status, 'running');
|
|
62
|
+
assert.deepEqual(session.agentProjection.conversation, [
|
|
63
|
+
{ role: 'user', content: 'Build docs' },
|
|
64
|
+
{ role: 'assistant', content: 'Working.' },
|
|
65
|
+
]);
|
|
62
66
|
assert.equal(session.headlessPlan[0].description, 'Build');
|
|
63
67
|
assert.equal(session.activities['production:job-1'].status, 'running');
|
|
64
68
|
assert.equal(session.productionActivity.jobId, 'job-1');
|
|
@@ -66,10 +70,7 @@ test('applyRuntimeStateToShellSession projects runtime state into shell session'
|
|
|
66
70
|
assert.equal(session.workflow.nodes[0].id, 'task:build');
|
|
67
71
|
assert.equal(session.workflow.relations[0].type, 'contains');
|
|
68
72
|
assert.deepEqual(session.workflow.waitingReasons, ['queue:q-1']);
|
|
69
|
-
assert.deepEqual(conversationMessages(session), [
|
|
70
|
-
{ role: 'user', content: 'Build docs' },
|
|
71
|
-
{ role: 'donna', content: 'Working.' },
|
|
72
|
-
]);
|
|
73
|
+
assert.deepEqual(conversationMessages(session), []);
|
|
73
74
|
});
|
|
74
75
|
|
|
75
76
|
test('submitRuntimeRun reports acceptance without throwing', async () => {
|
|
@@ -108,6 +109,26 @@ test('submitRuntimeRun routes busy runtime input through the control lane', asyn
|
|
|
108
109
|
}
|
|
109
110
|
});
|
|
110
111
|
|
|
112
|
+
test('submitRuntimeRun reports queued runs from non-blocking runtime', async () => {
|
|
113
|
+
const restore = stubFetch(async (url) => {
|
|
114
|
+
assert.equal(pathOf(url), '/run');
|
|
115
|
+
return jsonResponse(202, {
|
|
116
|
+
accepted: true,
|
|
117
|
+
queued: true,
|
|
118
|
+
kind: 'enqueue_run',
|
|
119
|
+
item: { id: 'control-1', status: 'queued' },
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
try {
|
|
123
|
+
const session = createSession();
|
|
124
|
+
const outcome = await submitRuntimeRun('build the doc', { runtime: { url: 'http://runtime.test' }, session });
|
|
125
|
+
assert.equal(outcome.kind, 'queued');
|
|
126
|
+
assert.equal(outcome.result.item.id, 'control-1');
|
|
127
|
+
} finally {
|
|
128
|
+
restore();
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
111
132
|
test('submitRuntimeRun reports a non-409 error without throwing or calling /control', async () => {
|
|
112
133
|
let controlCalled = false;
|
|
113
134
|
const restore = stubFetch(async (url) => {
|
|
@@ -162,7 +183,7 @@ test('agent mode without runtime records a visible error instead of falling back
|
|
|
162
183
|
|
|
163
184
|
test('runtime status exposes the disconnected reason', () => {
|
|
164
185
|
assert.equal(
|
|
165
|
-
runtimeStatusLine({ error: 'token mismatch' }, { workspace: '
|
|
186
|
+
runtimeStatusLine({ error: 'token mismatch' }, { workspace: 'acme' }),
|
|
166
187
|
'runtime: disconnected: token mismatch',
|
|
167
188
|
);
|
|
168
189
|
assert.equal(
|
package/src/shell/tui.tsx
CHANGED
|
@@ -336,6 +336,7 @@ function App(props: {
|
|
|
336
336
|
queueItems={state.queueItems()}
|
|
337
337
|
queueInfo={state.queueInfo()}
|
|
338
338
|
activeTab={state.rightTab()}
|
|
339
|
+
logFilter={state.runtimeLogFilter()}
|
|
339
340
|
onTabClick={state.selectRightTab}
|
|
340
341
|
/>
|
|
341
342
|
<SlashDialog context={state.activeEditor() ? null : state.slash()} />
|
package/src/shell/useAgent.ts
CHANGED
|
@@ -31,7 +31,11 @@ export function useAgent(props: { agent: unknown; packageJson: Record<string, un
|
|
|
31
31
|
props.onRuntimeAccepted?.();
|
|
32
32
|
props.addLog('runtime: run accepted');
|
|
33
33
|
} else if (outcome.kind === 'queued') {
|
|
34
|
-
|
|
34
|
+
// The server localizes control-lane acknowledgements from the
|
|
35
|
+
// session language (src/runtime/controlMessages.js) — always prefer
|
|
36
|
+
// its explanation over a local hardcoded string.
|
|
37
|
+
const explanation = (outcome as any).result?.explanation ?? 'Request added to the queue.';
|
|
38
|
+
conversationMessages(props.session).push({ role: 'command', content: String(explanation) });
|
|
35
39
|
props.addLog('runtime: control queued');
|
|
36
40
|
} else {
|
|
37
41
|
conversationMessages(props.session).push({ role: 'command', content: `Runtime error: ${outcome.message}` });
|
package/src/shell/useSession.ts
CHANGED
|
@@ -3,10 +3,11 @@ import { isAbsolute, relative, resolve, sep } from 'node:path';
|
|
|
3
3
|
import { createEffect, createMemo, createSignal, onCleanup } from 'solid-js';
|
|
4
4
|
import { formatMcpToolResult, callMcpTool } from '../core/mcp.js';
|
|
5
5
|
import { extractActivity, parseJsonText, sessionActivities } from '../core/activity.js';
|
|
6
|
-
import { formatPlanStatus, formatCompletedActivities } from '../core/plan.js';
|
|
6
|
+
import { formatPlanStatus, formatCompletedActivities, formatPlanStep } from '../core/plan.js';
|
|
7
7
|
import { createAgentEvent, dispatchAgentEvent } from '../core/agentEvents.js';
|
|
8
8
|
import { projectQueue, queueCounts, startNextQueuedJob, syncQueueWithActivity } from '../core/jobQueue.js';
|
|
9
9
|
import { queueStoreFor } from '../core/queueStore.js';
|
|
10
|
+
import { filterRuntimeLogs } from '../core/runtimeLog.js';
|
|
10
11
|
import { fetchRuntimeState, streamRuntimeEvents } from '../runtime/client.js';
|
|
11
12
|
import type { ActiveFileEditor } from './FileEditorDialog';
|
|
12
13
|
import {
|
|
@@ -99,11 +100,13 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
99
100
|
},
|
|
100
101
|
});
|
|
101
102
|
const runtimeRunStatus = createMemo(() => String(runtimeState()?.status ?? 'idle'));
|
|
102
|
-
const
|
|
103
|
+
const conversationBusy = createMemo(() => agent.busy());
|
|
104
|
+
const executionActive = createMemo(() =>
|
|
103
105
|
props.runtime?.url
|
|
104
|
-
? runtimeRunStatus() === 'running'
|
|
105
|
-
:
|
|
106
|
+
? runtimeRunStatus() === 'running'
|
|
107
|
+
: sessionActivities(session).some((activity) => !activity.terminal),
|
|
106
108
|
);
|
|
109
|
+
const agentBusy = conversationBusy;
|
|
107
110
|
const localFallbackActive = createMemo(() => !props.runtime?.url || runtimeStatus() === 'disconnected');
|
|
108
111
|
|
|
109
112
|
// Runtime conversation entries are merged into conversationMessages(session)
|
|
@@ -182,6 +185,20 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
182
185
|
let lastVisiblePlan: Array<{ step: number; description: string; status: string }> | null = null;
|
|
183
186
|
const activities = createMemo(() => {
|
|
184
187
|
version();
|
|
188
|
+
const activityLines = nonEmptyRuntimeArray(runtimeState()?.workflow?.activity?.lines);
|
|
189
|
+
if (activityLines) {
|
|
190
|
+
return activityLines.map((line: any, index: number) => ({
|
|
191
|
+
key: line.id ?? `activity-line-${index}`,
|
|
192
|
+
id: line.id ?? `activity-line-${index}`,
|
|
193
|
+
label: line.label,
|
|
194
|
+
status: line.status ?? 'running',
|
|
195
|
+
progress: line.progress ?? null,
|
|
196
|
+
terminal: ['done', 'failed', 'cancelled'].includes(String(line.status)),
|
|
197
|
+
_runtime: true,
|
|
198
|
+
_workflow: true,
|
|
199
|
+
_aggregated: true,
|
|
200
|
+
}));
|
|
201
|
+
}
|
|
185
202
|
const workflowActivities = nonEmptyRuntimeArray(runtimeState()?.workflow?.nodes?.filter((node: any) => node.type === 'activity'));
|
|
186
203
|
if (workflowActivities) {
|
|
187
204
|
return workflowActivities.map((node: any) => ({
|
|
@@ -207,36 +224,64 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
207
224
|
? lastVisibleActivities.map((activity) => ({ ...activity }))
|
|
208
225
|
: current;
|
|
209
226
|
});
|
|
227
|
+
// Control-lane run requests (user messages queued while the runtime is
|
|
228
|
+
// busy) live in state.controlQueue, a separate lane from the MCP job
|
|
229
|
+
// queue. They must appear in the Queue panel too — otherwise a queued run
|
|
230
|
+
// request is invisible (Queue (0)) even though it is registered and will
|
|
231
|
+
// start automatically.
|
|
232
|
+
const controlQueueItems = createMemo(() => {
|
|
233
|
+
version();
|
|
234
|
+
const controlQueue = runtimeState()?.controlQueue;
|
|
235
|
+
if (!Array.isArray(controlQueue)) return [] as any[];
|
|
236
|
+
return controlQueue
|
|
237
|
+
.filter((item: any) => ['queued', 'running', 'starting'].includes(String(item.status ?? '').toLowerCase()))
|
|
238
|
+
.map((item: any) => ({
|
|
239
|
+
...item,
|
|
240
|
+
id: item.id,
|
|
241
|
+
label: `run: ${String(item.input ?? '').slice(0, 64)}`,
|
|
242
|
+
status: item.status,
|
|
243
|
+
_runtime: true,
|
|
244
|
+
_control: true,
|
|
245
|
+
}));
|
|
246
|
+
});
|
|
210
247
|
const queueItems = createMemo(() => {
|
|
211
248
|
version();
|
|
249
|
+
const control = controlQueueItems();
|
|
212
250
|
const workflowQueue = nonEmptyRuntimeArray(runtimeState()?.workflow?.nodes?.filter((node: any) => node.type === 'queue'));
|
|
213
251
|
if (workflowQueue) {
|
|
214
|
-
return workflowQueue.map((node: any) => ({ ...(node.raw ?? {}), id: node.itemId ?? node.id, label: node.label, status: node.status, _runtime: true, _workflow: true }));
|
|
252
|
+
return [...control, ...workflowQueue.map((node: any) => ({ ...(node.raw ?? {}), id: node.itemId ?? node.id, label: node.label, status: node.status, _runtime: true, _workflow: true }))];
|
|
215
253
|
}
|
|
216
254
|
const runtimeQueue = nonEmptyRuntimeArray(runtimeState()?.queue);
|
|
217
|
-
if (runtimeQueue) return runtimeQueue.map((item: any) => ({ ...item, _runtime: true }));
|
|
218
|
-
return
|
|
219
|
-
|
|
255
|
+
if (runtimeQueue) return [...control, ...runtimeQueue.map((item: any) => ({ ...item, _runtime: true }))];
|
|
256
|
+
return [
|
|
257
|
+
...control,
|
|
258
|
+
...projectQueue((session as any).headlessPlan, (session as any).jobQueue ?? [], { workspace: (session as any).workspace ?? null })
|
|
259
|
+
.map((item: any) => ({ ...item })),
|
|
260
|
+
];
|
|
220
261
|
});
|
|
221
262
|
const queueInfo = createMemo(() => {
|
|
222
263
|
version();
|
|
264
|
+
const control = controlQueueItems();
|
|
265
|
+
const controlActive = control.length;
|
|
266
|
+
const controlCurrent = control.filter((item: any) => ['starting', 'running'].includes(String(item.status ?? '').toLowerCase())).length;
|
|
223
267
|
const workflowQueue = runtimeState()?.workflow?.nodes?.filter((node: any) => node.type === 'queue');
|
|
224
268
|
if (Array.isArray(workflowQueue)) {
|
|
225
269
|
return {
|
|
226
|
-
active: workflowQueue.filter((item: any) => ['waiting', 'starting', 'running', 'queued', 'pending', 'pending_approval'].includes(String(item.status ?? '').toLowerCase())).length,
|
|
227
|
-
current: workflowQueue.filter((item: any) => ['starting', 'running'].includes(String(item.status ?? '').toLowerCase())).length,
|
|
270
|
+
active: controlActive + workflowQueue.filter((item: any) => ['waiting', 'starting', 'running', 'queued', 'pending', 'pending_approval'].includes(String(item.status ?? '').toLowerCase())).length,
|
|
271
|
+
current: controlCurrent + workflowQueue.filter((item: any) => ['starting', 'running'].includes(String(item.status ?? '').toLowerCase())).length,
|
|
228
272
|
frozen: 0,
|
|
229
273
|
};
|
|
230
274
|
}
|
|
231
275
|
const runtimeQueue = runtimeState()?.queue;
|
|
232
276
|
if (Array.isArray(runtimeQueue)) {
|
|
233
277
|
return {
|
|
234
|
-
active: runtimeQueue.filter((item: any) => ['waiting', 'starting', 'running', 'queued', 'pending', 'pending_approval'].includes(String(item.status ?? '').toLowerCase())).length,
|
|
235
|
-
current: runtimeQueue.filter((item: any) => ['starting', 'running'].includes(String(item.status ?? '').toLowerCase())).length,
|
|
278
|
+
active: controlActive + runtimeQueue.filter((item: any) => ['waiting', 'starting', 'running', 'queued', 'pending', 'pending_approval'].includes(String(item.status ?? '').toLowerCase())).length,
|
|
279
|
+
current: controlCurrent + runtimeQueue.filter((item: any) => ['starting', 'running'].includes(String(item.status ?? '').toLowerCase())).length,
|
|
236
280
|
frozen: 0,
|
|
237
281
|
};
|
|
238
282
|
}
|
|
239
|
-
|
|
283
|
+
const base = queueCounts(session);
|
|
284
|
+
return { ...base, active: (base.active ?? 0) + controlActive, current: (base.current ?? 0) + controlCurrent };
|
|
240
285
|
});
|
|
241
286
|
const plan = createMemo(() => {
|
|
242
287
|
version();
|
|
@@ -246,7 +291,7 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
246
291
|
.sort((a: any, b: any) => Number(a.step ?? 0) - Number(b.step ?? 0))
|
|
247
292
|
.map((node: any, index: number) => ({
|
|
248
293
|
step: Number(node.step ?? index + 1),
|
|
249
|
-
description:
|
|
294
|
+
description: formatPlanStep(node) || `Step ${index + 1}`,
|
|
250
295
|
status: String(node.status ?? 'pending'),
|
|
251
296
|
}));
|
|
252
297
|
}
|
|
@@ -254,7 +299,7 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
254
299
|
if (runtimePlan) {
|
|
255
300
|
return runtimePlan.map((step: any, index: number) => ({
|
|
256
301
|
step: Number(step.step ?? index + 1),
|
|
257
|
-
description:
|
|
302
|
+
description: formatPlanStep(step) || `Step ${index + 1}`,
|
|
258
303
|
status: String(step.status ?? 'pending'),
|
|
259
304
|
}));
|
|
260
305
|
}
|
|
@@ -272,8 +317,9 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
272
317
|
version();
|
|
273
318
|
const runtimeLogs = runtimeState()?.logs;
|
|
274
319
|
if (!Array.isArray(runtimeLogs) || runtimeLogs.length === 0) return logs();
|
|
275
|
-
const
|
|
276
|
-
|
|
320
|
+
const filter = String((session as any).runtimeLogFilter ?? '').trim();
|
|
321
|
+
const tagged = filterRuntimeLogs(runtimeLogs.map((line: any) => `runtime ${String(line)}`), filter);
|
|
322
|
+
return [...logs(), ...tagged];
|
|
277
323
|
});
|
|
278
324
|
|
|
279
325
|
// Index-aligned with each workspace's runtimeConversation array — not just
|
|
@@ -291,6 +337,13 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
291
337
|
const target = conversationMessages(session);
|
|
292
338
|
const backed = runtimeConversationRefsByWorkspace.get(workspace) ?? [];
|
|
293
339
|
runtimeConversationRefsByWorkspace.set(workspace, backed);
|
|
340
|
+
if (backed.length === 0 && target.length === 0 && !runtimeConversation.some((message: any) => message?._pending)) {
|
|
341
|
+
backed.push(...runtimeConversation.map((message: any) => ({
|
|
342
|
+
role: message.role === 'assistant' ? 'donna' : message.role,
|
|
343
|
+
content: String(message.content ?? ''),
|
|
344
|
+
})));
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
294
347
|
for (let i = 0; i < runtimeConversation.length; i += 1) {
|
|
295
348
|
const message = runtimeConversation[i];
|
|
296
349
|
const content = String(message.content ?? '');
|
|
@@ -437,7 +490,7 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
437
490
|
const plan = (session as any).headlessPlan;
|
|
438
491
|
const stillRunning = sessionActivities(session).filter((a) => !a.terminal && a.poll);
|
|
439
492
|
const pendingSteps = (plan ?? []).filter((s: any) => s.status === 'pending');
|
|
440
|
-
if (stillRunning.length === 0 && pendingSteps.length > 0 && !
|
|
493
|
+
if (stillRunning.length === 0 && pendingSteps.length > 0 && !conversationBusy()) {
|
|
441
494
|
const completedAll = sessionActivities(session).filter((a) => a.terminal);
|
|
442
495
|
const prompt = buildContinuationPrompt(session, completedAll);
|
|
443
496
|
void agent.submit(prompt);
|
|
@@ -469,7 +522,7 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
469
522
|
|
|
470
523
|
async function submitInput(submittedValue?: string) {
|
|
471
524
|
const line = typeof submittedValue === 'string' && submittedValue.trim() ? submittedValue : input();
|
|
472
|
-
if (
|
|
525
|
+
if (conversationBusy()) return { exit: false, busy: true };
|
|
473
526
|
setInput('');
|
|
474
527
|
setConversationScroll(0);
|
|
475
528
|
if (line.trim()) {
|
|
@@ -593,6 +646,7 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
593
646
|
session,
|
|
594
647
|
messages,
|
|
595
648
|
logs: visibleLogs,
|
|
649
|
+
runtimeLogFilter: () => String((session as any).runtimeLogFilter ?? ''),
|
|
596
650
|
input,
|
|
597
651
|
setInput: updateInput,
|
|
598
652
|
title,
|
|
@@ -613,6 +667,8 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
613
667
|
plan,
|
|
614
668
|
conversationScroll,
|
|
615
669
|
scrollConversation,
|
|
670
|
+
conversationBusy,
|
|
671
|
+
executionActive,
|
|
616
672
|
busy: agentBusy,
|
|
617
673
|
abort,
|
|
618
674
|
submitInput,
|