newmark-agent 0.5.14 → 0.6.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/conversation-utility-host.bundle.cjs +4565 -2998
- package/dist/conversation-utility-host.js +1 -1
- package/dist/core/agent.d.ts +108 -16
- package/dist/core/agent.js +698 -195
- package/dist/core/agentKernel/agent.d.ts +1 -0
- package/dist/core/agentKernel/agent.js +9 -0
- package/dist/core/agentKernelDiagnostics.d.ts +4 -6
- package/dist/core/agentKernelDiagnostics.js +12 -9
- package/dist/core/agentKernelRunner.d.ts +5 -0
- package/dist/core/agentKernelRunner.js +250 -107
- package/dist/core/autoRouter.js +17 -27
- package/dist/core/config.d.ts +2 -0
- package/dist/core/config.js +7 -1
- package/dist/core/conversationCommandState.d.ts +17 -0
- package/dist/core/conversationCommandState.js +140 -0
- package/dist/core/conversationKernel.d.ts +25 -4
- package/dist/core/conversationKernel.js +360 -89
- package/dist/core/conversationListEvent.d.ts +6 -0
- package/dist/core/conversationListEvent.js +14 -0
- package/dist/core/electronUtilityAgentClient.d.ts +4 -1
- package/dist/core/electronUtilityAgentClient.js +4 -4
- package/dist/core/electronUtilityRuntimePool.d.ts +8 -2
- package/dist/core/electronUtilityRuntimePool.js +11 -5
- package/dist/core/installUpdate.js +41 -29
- package/dist/core/modelResponseHealth.d.ts +17 -0
- package/dist/core/modelResponseHealth.js +193 -0
- package/dist/core/providerUsageAccounting.d.ts +47 -0
- package/dist/core/providerUsageAccounting.js +71 -0
- package/dist/core/requestContextEstimate.d.ts +18 -0
- package/dist/core/requestContextEstimate.js +54 -0
- package/dist/core/subagent.d.ts +89 -5
- package/dist/core/subagent.js +264 -41
- package/dist/core/subagentCommunication.d.ts +43 -0
- package/dist/core/subagentCommunication.js +167 -0
- package/dist/core/types.d.ts +34 -1
- package/dist/core/utilityAgentProtocol.d.ts +4 -0
- package/dist/core/workEventCoalescer.js +4 -1
- package/dist/core/wslAgentClient.d.ts +18 -5
- package/dist/core/wslAgentClient.js +79 -27
- package/dist/core/wslAgentProtocol.d.ts +7 -0
- package/dist/core/wslAgentRuntimePool.d.ts +8 -2
- package/dist/core/wslAgentRuntimePool.js +19 -6
- package/dist/core/wslRuntimeProcessTree.d.ts +10 -0
- package/dist/core/wslRuntimeProcessTree.js +104 -0
- package/dist/llm/provider.d.ts +5 -12
- package/dist/llm/provider.js +150 -269
- package/dist/main.js +582 -356
- package/dist/preload.js +3 -3
- package/dist/providers/chat-completions.adapter.d.ts +2 -0
- package/dist/providers/chat-completions.adapter.js +84 -36
- package/dist/providers/provider-adapter.d.ts +2 -0
- package/dist/providers/provider-events.d.ts +23 -14
- package/dist/providers/provider-events.js +148 -43
- package/dist/providers/provider-headers.d.ts +7 -3
- package/dist/providers/provider-headers.js +109 -39
- package/dist/providers/provider-request-compat.d.ts +6 -0
- package/dist/providers/provider-request-compat.js +37 -0
- package/dist/providers/responses.adapter.d.ts +2 -0
- package/dist/providers/responses.adapter.js +192 -126
- package/dist/server.d.ts +9 -2
- package/dist/server.js +100 -24
- package/dist/tools/index.js +17 -1
- package/dist/ui/index.html +2515 -693
- package/dist/ui/lucide-sprite.svg +7 -0
- package/dist/ui/startup.html +6 -6
- package/dist/wsl-agent-host.bundle.cjs +4575 -3006
- package/dist/wsl-agent-host.js +9 -4
- package/package.json +23 -4
package/dist/main.js
CHANGED
|
@@ -41,6 +41,8 @@ const crypto_1 = require("crypto");
|
|
|
41
41
|
const child_process_1 = require("child_process");
|
|
42
42
|
const agent_1 = require("./core/agent");
|
|
43
43
|
const conversationKernel_1 = require("./core/conversationKernel");
|
|
44
|
+
const conversationCommandState_1 = require("./core/conversationCommandState");
|
|
45
|
+
const conversationListEvent_1 = require("./core/conversationListEvent");
|
|
44
46
|
const conversationTarget_1 = require("./core/conversationTarget");
|
|
45
47
|
const automation_1 = require("./core/automation");
|
|
46
48
|
const automationWake_1 = require("./core/automationWake");
|
|
@@ -107,7 +109,25 @@ function activeFlowStateKey(target) {
|
|
|
107
109
|
return (0, conversationTarget_1.normalizeConversationTarget)(target).runtimeKey;
|
|
108
110
|
}
|
|
109
111
|
function activeFlowStateFor(target) {
|
|
110
|
-
|
|
112
|
+
const key = activeFlowStateKey(target);
|
|
113
|
+
const current = activeFlowsByRuntimeKey.get(key);
|
|
114
|
+
if (current)
|
|
115
|
+
return current;
|
|
116
|
+
const saved = agent?.getStoredFlowSuspension(target.conversationId, (target.workspace || null));
|
|
117
|
+
if (!agent || !saved || (saved.target && activeFlowStateKey(saved.target) !== key))
|
|
118
|
+
return null;
|
|
119
|
+
const dir = path.join(agent.rootPath, 'Flow');
|
|
120
|
+
const found = flow_1.FlowEngine.findWorkflow(saved.workflowName, dir);
|
|
121
|
+
const workflow = found ? flow_1.FlowEngine.load(dir, found) : null;
|
|
122
|
+
if (!workflow)
|
|
123
|
+
return null;
|
|
124
|
+
const restored = {
|
|
125
|
+
workflow, input: saved.input, componentId: saved.componentId, completedResults: saved.completedResults,
|
|
126
|
+
previousMode: saved.previousMode, previousFlow: null, previousPc: 0, queueWasPaused: saved.queueWasPaused,
|
|
127
|
+
reason: saved.reason, message: saved.message, target, abortController: null, name: workflow.name, flowAgent: null,
|
|
128
|
+
};
|
|
129
|
+
activeFlowsByRuntimeKey.set(key, restored);
|
|
130
|
+
return restored;
|
|
111
131
|
}
|
|
112
132
|
let wslAgentClient = null;
|
|
113
133
|
let electronUtilityRuntimePool = null;
|
|
@@ -216,8 +236,9 @@ async function runShellCommand(command, shellId, cwd) {
|
|
|
216
236
|
return { output };
|
|
217
237
|
}
|
|
218
238
|
function resetConversationKernel() {
|
|
219
|
-
if (conversationKernel?.
|
|
239
|
+
if (conversationKernel?.hasRetainedWork())
|
|
220
240
|
return;
|
|
241
|
+
conversationKernel?.disposeIdle();
|
|
221
242
|
conversationKernel = null;
|
|
222
243
|
}
|
|
223
244
|
function runCommandAsync(command, args, cwd, timeoutMs = 30000) {
|
|
@@ -339,8 +360,13 @@ function dispatchAgentWorkEvent(event, mirrorToMobile = true) {
|
|
|
339
360
|
}
|
|
340
361
|
const workEventCoalescer = new workEventCoalescer_1.WorkEventCoalescer(event => dispatchAgentWorkEvent(event, true));
|
|
341
362
|
const workEventNoMobileCoalescer = new workEventCoalescer_1.WorkEventCoalescer(event => dispatchAgentWorkEvent(event, false));
|
|
363
|
+
let projectConversationEvent = null;
|
|
364
|
+
function publishConversationList(workspace = agent?.workspace.current || null) {
|
|
365
|
+
if (agent && workspace)
|
|
366
|
+
broadcastAgentWorkEvent((0, conversationListEvent_1.conversationListEvent)(agent, workspace));
|
|
367
|
+
}
|
|
342
368
|
function broadcastAgentWorkEvent(event, mirrorToMobile = true) {
|
|
343
|
-
const workEvent = event;
|
|
369
|
+
const workEvent = projectConversationEvent?.(event) || event;
|
|
344
370
|
// Response and thought deltas are coalesced at the IPC/SSE boundary; all
|
|
345
371
|
// lifecycle/tool events retain immediate ordering.
|
|
346
372
|
if (workEvent.type === 'text' || workEvent.type === 'thought_delta') {
|
|
@@ -349,7 +375,7 @@ function broadcastAgentWorkEvent(event, mirrorToMobile = true) {
|
|
|
349
375
|
}
|
|
350
376
|
workEventCoalescer.flushAll();
|
|
351
377
|
workEventNoMobileCoalescer.flushAll();
|
|
352
|
-
dispatchAgentWorkEvent(
|
|
378
|
+
dispatchAgentWorkEvent(workEvent, mirrorToMobile);
|
|
353
379
|
}
|
|
354
380
|
function ensureConversationKernel(root) {
|
|
355
381
|
if (!agent)
|
|
@@ -367,6 +393,7 @@ function persistedFlowSuspensionRecord(suspension, message = '') {
|
|
|
367
393
|
input: String(suspension.input || ''),
|
|
368
394
|
completedResults: Array.isArray(suspension.completedResults) ? suspension.completedResults : [],
|
|
369
395
|
previousMode: suspension.previousMode,
|
|
396
|
+
queueWasPaused: suspension.queueWasPaused,
|
|
370
397
|
reason: suspension.reason === '' ? 'interrupted' : suspension.reason,
|
|
371
398
|
message: String(message || suspension.message || ''),
|
|
372
399
|
target: suspension.target ? {
|
|
@@ -1303,12 +1330,12 @@ function viewerDocument(request) {
|
|
|
1303
1330
|
? `<img src="${viewerEscape(request.dataUrl)}" alt="${title}">`
|
|
1304
1331
|
: '<div class="empty">Image unavailable</div>';
|
|
1305
1332
|
return `<!doctype html><html><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src data:; style-src 'unsafe-inline'"><title>${title}</title><style>
|
|
1306
|
-
:root{color-scheme:dark}*{box-sizing:border-box}html,body{margin:0;width:100%;height:100%;overflow:hidden;background:#
|
|
1333
|
+
:root{color-scheme:dark}*{box-sizing:border-box}html,body{margin:0;width:100%;height:100%;overflow:hidden;background:#101010;color:#f2f2f2;font:12px system-ui,sans-serif}main{height:100%;display:grid;grid-template-rows:auto 1fr;padding:14px;gap:10px}header{font-weight:650;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#cecece}section{min-height:0;display:flex;align-items:center;justify-content:center;border:1px solid #383838;border-radius:10px;background:#181818}img{display:block;max-width:100%;max-height:100%;object-fit:contain}svg{width:100%;height:100%}.edges line{stroke:#484848;stroke-width:1;opacity:.62}text{fill:#cecece;font-size:9px}.empty{color:#949494}</style></head><body><main><header>${title}</header><section>${image}</section></main></body></html>`;
|
|
1307
1334
|
}
|
|
1308
1335
|
async function createViewerWindow(request) {
|
|
1309
1336
|
const viewerRequest = request && typeof request === 'object' ? request : {};
|
|
1310
1337
|
const windowTitle = String(viewerRequest.title || viewerRequest.caption || (viewerRequest.type === 'memory-overview' ? 'Memory Lab Overview' : '示意图')).trim().slice(0, 160) || '示意图';
|
|
1311
|
-
const win = new electron_1.BrowserWindow({ width: 760, height: 600, minWidth: 420, minHeight: 320, show: false, autoHideMenuBar: true, title: windowTitle, backgroundColor: '#
|
|
1338
|
+
const win = new electron_1.BrowserWindow({ width: 760, height: 600, minWidth: 420, minHeight: 320, show: false, autoHideMenuBar: true, title: windowTitle, backgroundColor: '#101010', webPreferences: { contextIsolation: true, nodeIntegration: false, sandbox: true } });
|
|
1312
1339
|
if (viewerRequest.type === 'memory-overview') {
|
|
1313
1340
|
await win.loadFile(path.join(__dirname, 'ui', 'index.html'), {
|
|
1314
1341
|
query: { newmarkViewer: 'memory-overview', viewerTitle: windowTitle },
|
|
@@ -1827,7 +1854,7 @@ else {
|
|
|
1827
1854
|
frame: false,
|
|
1828
1855
|
title: 'Newmark Agent',
|
|
1829
1856
|
icon: themedAppIconPath(),
|
|
1830
|
-
backgroundColor: '#
|
|
1857
|
+
backgroundColor: '#101010',
|
|
1831
1858
|
webPreferences: {
|
|
1832
1859
|
preload: path.join(__dirname, 'preload.js'),
|
|
1833
1860
|
contextIsolation: true,
|
|
@@ -1989,6 +2016,7 @@ else {
|
|
|
1989
2016
|
componentId: Math.max(0, Math.floor(Number(stored.componentId) || 0)),
|
|
1990
2017
|
completedResults: Array.isArray(stored.completedResults) ? stored.completedResults : [],
|
|
1991
2018
|
previousMode: stored.previousMode === 'plan' || stored.previousMode === 'goal' ? stored.previousMode : 'build',
|
|
2019
|
+
queueWasPaused: stored.queueWasPaused,
|
|
1992
2020
|
previousFlow: null,
|
|
1993
2021
|
previousPc: 0,
|
|
1994
2022
|
reason,
|
|
@@ -2053,179 +2081,9 @@ else {
|
|
|
2053
2081
|
mobileServerWorkEventSubscribers.add(listener);
|
|
2054
2082
|
return () => mobileServerWorkEventSubscribers.delete(listener);
|
|
2055
2083
|
},
|
|
2056
|
-
conversationUiState: async (requested) =>
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
? await ensureWslConversationPool().snapshot(target)
|
|
2060
|
-
: await ensureElectronUtilityPool().snapshot(target);
|
|
2061
|
-
const flowRunning = flowRunningForTarget(target);
|
|
2062
|
-
const flowSuspension = flowSuspensionForTarget(target);
|
|
2063
|
-
return {
|
|
2064
|
-
...snapshot,
|
|
2065
|
-
flow: flowRunning ? {
|
|
2066
|
-
running: true,
|
|
2067
|
-
paused: false,
|
|
2068
|
-
name: flowRunning.name,
|
|
2069
|
-
promptText: String(activeFlowStateFor(target)?.input || ''),
|
|
2070
|
-
message: '',
|
|
2071
|
-
} : flowSuspension ? {
|
|
2072
|
-
running: true,
|
|
2073
|
-
paused: true,
|
|
2074
|
-
name: flowSuspension.workflowName || '',
|
|
2075
|
-
promptText: String(flowSuspension.input || ''),
|
|
2076
|
-
message: String(flowSuspension.message || ''),
|
|
2077
|
-
reason: flowSuspension.reason || '',
|
|
2078
|
-
} : null,
|
|
2079
|
-
};
|
|
2080
|
-
},
|
|
2081
|
-
conversationUiAction: async (requested, action, value, input) => {
|
|
2082
|
-
const target = conversationRuntimeTarget(requested);
|
|
2083
|
-
if (action.startsWith('queue_')) {
|
|
2084
|
-
const queueAction = action === 'queue_enqueue' ? 'enqueue'
|
|
2085
|
-
: action === 'queue_update' ? 'update'
|
|
2086
|
-
: action === 'queue_delete' ? 'delete'
|
|
2087
|
-
: action === 'queue_reorder' ? 'reorder'
|
|
2088
|
-
: action === 'queue_toggle_pause' ? 'toggle_pause'
|
|
2089
|
-
: 'guide';
|
|
2090
|
-
const queueInput = {
|
|
2091
|
-
id: String(input?.id || ''),
|
|
2092
|
-
text: String(input?.text || value || ''),
|
|
2093
|
-
requestedMode: String(input?.requestedMode || 'build'),
|
|
2094
|
-
goalObjective: String(input?.goalObjective || ''),
|
|
2095
|
-
createdAt: String(input?.createdAt || ''),
|
|
2096
|
-
orderedIds: Array.isArray(input?.orderedIds)
|
|
2097
|
-
? input.orderedIds.map((id) => String(id || ''))
|
|
2098
|
-
: undefined,
|
|
2099
|
-
};
|
|
2100
|
-
return wslBackendEnabled()
|
|
2101
|
-
? await ensureWslConversationPool().queueAction(target, queueAction, queueInput)
|
|
2102
|
-
: await ensureElectronUtilityPool().queueAction(target, queueAction, queueInput);
|
|
2103
|
-
}
|
|
2104
|
-
if (action === 'goal_update') {
|
|
2105
|
-
return { goal: await mutateTargetConversation(target, () => {
|
|
2106
|
-
const isolated = isolatedConversationAgent(target);
|
|
2107
|
-
isolated.updateGoal(String(value || '').trim());
|
|
2108
|
-
isolated.setMode('goal');
|
|
2109
|
-
isolated.saveWorkspaceConversationState(true);
|
|
2110
|
-
return isolated.getConversationSnapshot(target.conversationId).goal;
|
|
2111
|
-
}) };
|
|
2112
|
-
}
|
|
2113
|
-
if (action === 'goal_guide' || action === 'conversation_guide') {
|
|
2114
|
-
const objective = action === 'goal_guide' ? String(value || '').trim() : '';
|
|
2115
|
-
const guideText = String(value || '').trim();
|
|
2116
|
-
const snapshot = wslBackendEnabled()
|
|
2117
|
-
? await ensureWslConversationPool().snapshot(target)
|
|
2118
|
-
: await ensureElectronUtilityPool().snapshot(target);
|
|
2119
|
-
const runtime = snapshot.runtime;
|
|
2120
|
-
// `running` may turn false one IPC task before a finalization-
|
|
2121
|
-
// window Guide reaches the worker. Preserve the authoritative
|
|
2122
|
-
// runId and let ConversationKernel decide whether that run can
|
|
2123
|
-
// be reactivated or must reject a genuinely stale request.
|
|
2124
|
-
if (!guideText || !runtime?.runId) {
|
|
2125
|
-
return { ok: false, error: action === 'goal_guide'
|
|
2126
|
-
? 'There is no active Build for this Goal Guide.'
|
|
2127
|
-
: 'Target conversation is not running.' };
|
|
2128
|
-
}
|
|
2129
|
-
const prefix = 'Goal for the current Build:\n';
|
|
2130
|
-
const now = new Date().toISOString();
|
|
2131
|
-
const envelope = {
|
|
2132
|
-
clientMessageId: (0, crypto_1.randomUUID)(),
|
|
2133
|
-
guideId: (0, crypto_1.randomUUID)(),
|
|
2134
|
-
target: { workspaceId: target.workspaceId, conversationId: target.conversationId },
|
|
2135
|
-
runId: runtime.runId,
|
|
2136
|
-
deliveryMode: 'steer',
|
|
2137
|
-
text: objective ? prefix + objective : guideText,
|
|
2138
|
-
goalObjective: objective || undefined,
|
|
2139
|
-
createdAt: now,
|
|
2140
|
-
};
|
|
2141
|
-
const receipt = wslBackendEnabled()
|
|
2142
|
-
? await ensureWslConversationPool().enqueueGuide(envelope)
|
|
2143
|
-
: await ensureElectronUtilityPool().enqueueGuide(envelope);
|
|
2144
|
-
return { ok: receipt.status !== 'rejected', receipt };
|
|
2145
|
-
}
|
|
2146
|
-
if (action === 'goal_toggle_pause') {
|
|
2147
|
-
const resident = wslBackendEnabled()
|
|
2148
|
-
? await ensureWslConversationPool().toggleGoalPause(target)
|
|
2149
|
-
: await ensureElectronUtilityPool().toggleGoalPause(target);
|
|
2150
|
-
const paused = resident !== null ? resident : await mutateTargetConversation(target, () => isolatedConversationAgent(target).toggleGoalPause());
|
|
2151
|
-
return { ok: true, paused };
|
|
2152
|
-
}
|
|
2153
|
-
if (action === 'goal_clear') {
|
|
2154
|
-
const resident = wslBackendEnabled()
|
|
2155
|
-
? await ensureWslConversationPool().clearGoal(target)
|
|
2156
|
-
: await ensureElectronUtilityPool().clearGoal(target);
|
|
2157
|
-
if (resident === null)
|
|
2158
|
-
await mutateTargetConversation(target, () => isolatedConversationAgent(target).clearGoal());
|
|
2159
|
-
return { ok: true, cleared: true };
|
|
2160
|
-
}
|
|
2161
|
-
if (action === 'flow_pause')
|
|
2162
|
-
return await stopFlowForTarget(target);
|
|
2163
|
-
if (action === 'flow_resume')
|
|
2164
|
-
return await resumeFlowForTarget(String(value || ''), target);
|
|
2165
|
-
if (action === 'conversation_stop') {
|
|
2166
|
-
const snapshot = wslBackendEnabled()
|
|
2167
|
-
? await ensureWslConversationPool().snapshot(target)
|
|
2168
|
-
: await ensureElectronUtilityPool().snapshot(target);
|
|
2169
|
-
const runtime = snapshot.runtime;
|
|
2170
|
-
return wslBackendEnabled()
|
|
2171
|
-
? await ensureWslConversationPool().requestStop(target, runtime?.runId)
|
|
2172
|
-
: await ensureElectronUtilityPool().requestStop(target, runtime?.runId);
|
|
2173
|
-
}
|
|
2174
|
-
if (action === 'input_mode') {
|
|
2175
|
-
const mode = String(value || '') === 'next' ? 'next' : 'guide';
|
|
2176
|
-
const selected = wslBackendEnabled()
|
|
2177
|
-
? await ensureWslConversationPool().setInputMode(target, mode)
|
|
2178
|
-
: await ensureElectronUtilityPool().setInputMode(target, mode);
|
|
2179
|
-
return { ok: true, inputMode: selected || mode };
|
|
2180
|
-
}
|
|
2181
|
-
const flow = activeFlowStateFor(target);
|
|
2182
|
-
if (!flow?.abortController || !flow.flowAgent)
|
|
2183
|
-
return { ok: false, error: 'No active Flow accepts Guide input.' };
|
|
2184
|
-
const accepted = flow.flowAgent.queueActiveKernelMessage(String(value || '').trim(), 'steer') || false;
|
|
2185
|
-
return accepted ? { ok: true, accepted: true, flow: flow.name } : { ok: false, error: 'The current Flow Build is not accepting Guide input.' };
|
|
2186
|
-
},
|
|
2187
|
-
conversationPrompt: async (requested, message, requestedOptions) => {
|
|
2188
|
-
const target = conversationRuntimeTarget(requested);
|
|
2189
|
-
if (activeFlowStateFor(target))
|
|
2190
|
-
clearFlowSuspensionForNewWork(target);
|
|
2191
|
-
const snapshot = wslBackendEnabled()
|
|
2192
|
-
? await ensureWslConversationPool().snapshot(target)
|
|
2193
|
-
: await ensureElectronUtilityPool().snapshot(target);
|
|
2194
|
-
const requestedMode = String(requestedOptions?.requestedMode || '').toLowerCase();
|
|
2195
|
-
const goalObjective = String(requestedOptions?.goalObjective || '').trim();
|
|
2196
|
-
const mode = (goalObjective || requestedMode === 'goal') ? 'build' : String(snapshot.mode || 'build');
|
|
2197
|
-
const inputMode = String(requestedOptions?.inputMode || snapshot.inputMode || 'guide') === 'next' ? 'next' : 'guide';
|
|
2198
|
-
const options = {
|
|
2199
|
-
mode,
|
|
2200
|
-
model: String(snapshot.model || agent.ensureUsableModelSelection()),
|
|
2201
|
-
intelligence: String(snapshot.intelligence || agent.intelligence),
|
|
2202
|
-
inputMode,
|
|
2203
|
-
engine: agent.engine,
|
|
2204
|
-
};
|
|
2205
|
-
const queueMode = inputMode === 'guide' ? 'steer' : 'followUp';
|
|
2206
|
-
const promptMessage = goalObjective ? {
|
|
2207
|
-
text: message,
|
|
2208
|
-
visibleMode: 'goal',
|
|
2209
|
-
goalObjective,
|
|
2210
|
-
} : message;
|
|
2211
|
-
const result = wslBackendEnabled()
|
|
2212
|
-
? await ensureWslConversationPool().prompt({
|
|
2213
|
-
message: promptMessage,
|
|
2214
|
-
target,
|
|
2215
|
-
conversationId: target.conversationId,
|
|
2216
|
-
options,
|
|
2217
|
-
queueMode,
|
|
2218
|
-
workspace: target.workspace ? {
|
|
2219
|
-
id: target.workspace.id,
|
|
2220
|
-
name: target.workspace.name,
|
|
2221
|
-
path: target.workspace.path,
|
|
2222
|
-
isInternal: target.workspace.isInternal,
|
|
2223
|
-
kind: target.workspace.kind,
|
|
2224
|
-
} : null,
|
|
2225
|
-
})
|
|
2226
|
-
: await ensureElectronUtilityPool().prompt({ message: promptMessage, target, options, queueMode });
|
|
2227
|
-
return { ...result };
|
|
2228
|
-
},
|
|
2084
|
+
conversationUiState: async (requested, options) => await runtimeSnapshotForTarget(conversationRuntimeTarget(requested), options),
|
|
2085
|
+
conversationUiAction: async (requested, action, value, input) => await applyConversationAction(requested, action, value, input),
|
|
2086
|
+
conversationPrompt: async (requested, message, options) => await submitConversationCommand(message, requested, options),
|
|
2229
2087
|
});
|
|
2230
2088
|
if (agent.config.getBool('remote', 'touch_enabled')) {
|
|
2231
2089
|
runServer(root);
|
|
@@ -2237,7 +2095,7 @@ else {
|
|
|
2237
2095
|
}
|
|
2238
2096
|
}
|
|
2239
2097
|
};
|
|
2240
|
-
const localConversationSnapshotForStartup = (target) => {
|
|
2098
|
+
const localConversationSnapshotForStartup = (target, options = {}) => {
|
|
2241
2099
|
const startupAgent = agent;
|
|
2242
2100
|
if (!startupAgent)
|
|
2243
2101
|
throw new Error('Agent is unavailable for startup conversation hydration');
|
|
@@ -2246,7 +2104,7 @@ else {
|
|
|
2246
2104
|
if (normalizedTarget.runtimeKey !== currentTarget.runtimeKey) {
|
|
2247
2105
|
throw new Error('Startup hydration requested a conversation other than the current Agent conversation');
|
|
2248
2106
|
}
|
|
2249
|
-
const conversationSnapshot = startupAgent.ensureConversationSnapshot(target.conversationId);
|
|
2107
|
+
const conversationSnapshot = startupAgent.ensureConversationSnapshot(target.conversationId, options);
|
|
2250
2108
|
if (conversationSnapshot.conversationId !== normalizedTarget.conversationId) {
|
|
2251
2109
|
throw new Error('Startup conversation snapshot identity mismatch');
|
|
2252
2110
|
}
|
|
@@ -2835,7 +2693,7 @@ else {
|
|
|
2835
2693
|
if (state && (state.reason === 'question' || state.reason === 'interrupted')) {
|
|
2836
2694
|
return persistedFlowSuspensionRecord(state, state.message || '');
|
|
2837
2695
|
}
|
|
2838
|
-
const stored = agent?.getStoredFlowSuspension(target.conversationId);
|
|
2696
|
+
const stored = agent?.getStoredFlowSuspension(target.conversationId, (target.workspace || null));
|
|
2839
2697
|
if (!stored)
|
|
2840
2698
|
return null;
|
|
2841
2699
|
if (!stored.target)
|
|
@@ -2846,6 +2704,9 @@ else {
|
|
|
2846
2704
|
};
|
|
2847
2705
|
const flowRunningForTarget = (target) => {
|
|
2848
2706
|
const state = activeFlowStateFor(target);
|
|
2707
|
+
const pending = pendingFlowStarts.get(activeFlowStateKey(target));
|
|
2708
|
+
if (!state && pending)
|
|
2709
|
+
return { name: pending.name, promptText: pending.input, waiting: true };
|
|
2849
2710
|
if (!state || state.reason === 'question' || state.reason === 'interrupted')
|
|
2850
2711
|
return null;
|
|
2851
2712
|
return { name: state.name || state.workflow.name || '' };
|
|
@@ -2854,7 +2715,12 @@ else {
|
|
|
2854
2715
|
workspaceId: owner.workspace.current?.id || 'none',
|
|
2855
2716
|
conversationId: owner.activeConversationId || 'default',
|
|
2856
2717
|
});
|
|
2857
|
-
const setTargetFlowMode = async (target, mode) => {
|
|
2718
|
+
const setTargetFlowMode = async (target, mode, restoreSelection = false) => {
|
|
2719
|
+
if (restoreSelection) {
|
|
2720
|
+
const selected = conversationSelections.get(activeFlowStateKey(target))?.mode;
|
|
2721
|
+
mode = selected && selected !== 'flow' ? selected : mode === 'flow' ? 'build' : mode;
|
|
2722
|
+
conversationSelections.set(activeFlowStateKey(target), { mode });
|
|
2723
|
+
}
|
|
2858
2724
|
conversationKernel?.setMode(target, mode);
|
|
2859
2725
|
if (wslBackendEnabled())
|
|
2860
2726
|
await ensureWslConversationPool().setMode(target, mode);
|
|
@@ -2877,22 +2743,8 @@ else {
|
|
|
2877
2743
|
owner.flowPc = suspension.previousPc;
|
|
2878
2744
|
owner.setMode(suspension.previousMode);
|
|
2879
2745
|
}
|
|
2880
|
-
agent.clearStoredFlowSuspension(suspension.target.conversationId);
|
|
2881
|
-
await setTargetFlowMode(suspension.target, suspension.previousMode);
|
|
2882
|
-
};
|
|
2883
|
-
// Drops a paused Flow for exactly one conversation without restoring the
|
|
2884
|
-
// previous mode so a user's new Build/Plan/Goal/Flow instruction owns the
|
|
2885
|
-
// current mode. The renderer keeps the mode in sync through agent:setMode
|
|
2886
|
-
// before the send lands. Other conversations' Flow state is untouched.
|
|
2887
|
-
const clearFlowSuspensionForNewWork = (target) => {
|
|
2888
|
-
if (!agent)
|
|
2889
|
-
return;
|
|
2890
|
-
const key = activeFlowStateKey(target);
|
|
2891
|
-
const state = activeFlowsByRuntimeKey.get(key) || null;
|
|
2892
|
-
if (!state)
|
|
2893
|
-
return;
|
|
2894
|
-
activeFlowsByRuntimeKey.delete(key);
|
|
2895
|
-
agent.clearStoredFlowSuspension(state.target.conversationId);
|
|
2746
|
+
agent.clearStoredFlowSuspension(suspension.target.conversationId, (suspension.target.workspace || null));
|
|
2747
|
+
await setTargetFlowMode(suspension.target, suspension.previousMode, true);
|
|
2896
2748
|
};
|
|
2897
2749
|
// Archive is a destructive lifecycle boundary. It must cancel the Flow
|
|
2898
2750
|
// owner itself before touching the runtime pool; Flow runs bypass the
|
|
@@ -3066,10 +2918,58 @@ else {
|
|
|
3066
2918
|
throw new Error('This conversation or workspace is being mutated. Retry after the operation completes.');
|
|
3067
2919
|
}
|
|
3068
2920
|
};
|
|
3069
|
-
const
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
2921
|
+
const conversationSelections = new conversationCommandState_1.ConversationCommandStateStore(root);
|
|
2922
|
+
const conversationCommands = new Map();
|
|
2923
|
+
const mainConversationOwners = new Set();
|
|
2924
|
+
const pendingFlowStarts = new Map();
|
|
2925
|
+
projectConversationEvent = event => {
|
|
2926
|
+
if (event.type !== 'queue_update' || !event.workspaceId || !event.conversationId)
|
|
2927
|
+
return event;
|
|
2928
|
+
const target = conversationRuntimeTarget({ workspaceId: event.workspaceId, conversationId: event.conversationId });
|
|
2929
|
+
const selected = conversationSelections.get(activeFlowStateKey(target));
|
|
2930
|
+
if (typeof event.queuePaused === 'boolean')
|
|
2931
|
+
conversationSelections.set(activeFlowStateKey(target), { queuePaused: event.queuePaused });
|
|
2932
|
+
return {
|
|
2933
|
+
...event,
|
|
2934
|
+
stateScope: 'conversation',
|
|
2935
|
+
mode: selected?.mode || (activeFlowStateFor(target) ? 'flow' : event.mode),
|
|
2936
|
+
inputMode: selected?.inputMode,
|
|
2937
|
+
flowRunning: flowRunningForTarget(target),
|
|
2938
|
+
flowSuspension: flowSuspensionForTarget(target),
|
|
2939
|
+
};
|
|
2940
|
+
};
|
|
2941
|
+
const runtimeSnapshotForTarget = async (target, options = {}) => {
|
|
2942
|
+
const key = activeFlowStateKey(target);
|
|
2943
|
+
let snapshot = mainConversationOwners.has(key)
|
|
2944
|
+
? ensureConversationKernel(root).snapshot(target, options)
|
|
2945
|
+
: wslBackendEnabled()
|
|
2946
|
+
? await ensureWslConversationPool().snapshot(target, options)
|
|
2947
|
+
: await ensureElectronUtilityPool().snapshot(target, options);
|
|
2948
|
+
const flow = activeFlowStateFor(target);
|
|
2949
|
+
const selected = conversationSelections.get(key);
|
|
2950
|
+
if (!flow && !pendingFlowStarts.has(key) && selected?.queuePaused === true && !snapshot.queuePaused) {
|
|
2951
|
+
if (mainConversationOwners.has(key))
|
|
2952
|
+
ensureConversationKernel(root).queueAction(target, 'set_pause', { paused: true });
|
|
2953
|
+
else if (wslBackendEnabled())
|
|
2954
|
+
await ensureWslConversationPool().queueAction(target, 'set_pause', { paused: true });
|
|
2955
|
+
else
|
|
2956
|
+
await ensureElectronUtilityPool().queueAction(target, 'set_pause', { paused: true });
|
|
2957
|
+
snapshot = mainConversationOwners.has(key) ? ensureConversationKernel(root).snapshot(target, options)
|
|
2958
|
+
: wslBackendEnabled() ? await ensureWslConversationPool().snapshot(target, options) : await ensureElectronUtilityPool().snapshot(target, options);
|
|
2959
|
+
}
|
|
2960
|
+
return {
|
|
2961
|
+
...snapshot,
|
|
2962
|
+
executionMode: snapshot.mode,
|
|
2963
|
+
mode: selected?.mode || (flow || pendingFlowStarts.has(key) ? 'flow' : snapshot.mode),
|
|
2964
|
+
inputMode: selected?.inputMode || snapshot.inputMode || 'guide',
|
|
2965
|
+
flow: flow ? {
|
|
2966
|
+
running: true, paused: flow.reason === 'interrupted', name: flow.name,
|
|
2967
|
+
promptText: flow.input, message: flow.message || '', reason: flow.reason,
|
|
2968
|
+
} : pendingFlowStarts.has(key) ? {
|
|
2969
|
+
running: true, paused: false, name: pendingFlowStarts.get(key).name,
|
|
2970
|
+
promptText: pendingFlowStarts.get(key).input, message: '', reason: '', waiting: true,
|
|
2971
|
+
} : null,
|
|
2972
|
+
};
|
|
3073
2973
|
};
|
|
3074
2974
|
const runtimeIsRestarting = (target) => wslBackendEnabled()
|
|
3075
2975
|
? !!wslAgentRuntimePool?.isRestarting(target)
|
|
@@ -3090,9 +2990,16 @@ else {
|
|
|
3090
2990
|
throw new Error('Cannot mutate a conversation while its runtime is running or stopping.');
|
|
3091
2991
|
}
|
|
3092
2992
|
};
|
|
3093
|
-
const peekTargetRuntime = (target) =>
|
|
3094
|
-
|
|
3095
|
-
|
|
2993
|
+
const peekTargetRuntime = (target) => {
|
|
2994
|
+
if (mainConversationOwners.has(activeFlowStateKey(target))) {
|
|
2995
|
+
const runtime = conversationKernel?.runtimeState(target);
|
|
2996
|
+
if (runtime)
|
|
2997
|
+
return { resident: true, running: runtime.running, stopping: runtime.stopRequested, connected: true };
|
|
2998
|
+
}
|
|
2999
|
+
return wslBackendEnabled()
|
|
3000
|
+
? (wslAgentRuntimePool?.peek(target) || { resident: false, running: false, stopping: false, connected: false })
|
|
3001
|
+
: (electronUtilityRuntimePool?.peek(target) || { resident: false, running: false, stopping: false, connected: false });
|
|
3002
|
+
};
|
|
3096
3003
|
const stopTargetRuntime = async (target) => {
|
|
3097
3004
|
if (wslBackendEnabled())
|
|
3098
3005
|
await wslAgentRuntimePool?.stopTarget(target);
|
|
@@ -3128,6 +3035,14 @@ else {
|
|
|
3128
3035
|
isolated.setConversation(normalized.conversationId);
|
|
3129
3036
|
return isolated;
|
|
3130
3037
|
};
|
|
3038
|
+
const conversationAgentForTarget = (target) => {
|
|
3039
|
+
if (mainConversationOwners.has(activeFlowStateKey(target))) {
|
|
3040
|
+
const owner = conversationKernel?.conversationOwner(target);
|
|
3041
|
+
if (owner)
|
|
3042
|
+
return owner;
|
|
3043
|
+
}
|
|
3044
|
+
return isolatedConversationAgent(target);
|
|
3045
|
+
};
|
|
3131
3046
|
const mutateTargetConversation = async (target, mutation) => {
|
|
3132
3047
|
const normalized = (0, conversationTarget_1.normalizeConversationTarget)(target);
|
|
3133
3048
|
assertTargetNotMutating(normalized);
|
|
@@ -3142,7 +3057,213 @@ else {
|
|
|
3142
3057
|
mutatingRuntimeKeys.delete(normalized.runtimeKey);
|
|
3143
3058
|
}
|
|
3144
3059
|
};
|
|
3145
|
-
|
|
3060
|
+
const selectConversationMode = async (target, raw) => {
|
|
3061
|
+
const mode = (['build', 'plan', 'chat', 'goal', 'flow'].includes(raw) ? raw : 'build');
|
|
3062
|
+
const key = activeFlowStateKey(target);
|
|
3063
|
+
conversationSelections.set(key, { ...conversationSelections.get(key), mode });
|
|
3064
|
+
if (!activeFlowStateFor(target)) {
|
|
3065
|
+
if (mainConversationOwners.has(key))
|
|
3066
|
+
ensureConversationKernel(root).setMode(target, mode);
|
|
3067
|
+
else {
|
|
3068
|
+
const result = wslBackendEnabled() ? await ensureWslConversationPool().setMode(target, mode) : await ensureElectronUtilityPool().setMode(target, mode);
|
|
3069
|
+
if (result === null)
|
|
3070
|
+
isolatedConversationAgent(target).selectConversationMode(mode);
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
if (agent && activeFlowStateKey(conversationRuntimeTarget(agent.activeConversationId || 'default')) === key)
|
|
3074
|
+
agent.mode = mode;
|
|
3075
|
+
await publishConversationState(target);
|
|
3076
|
+
return mode;
|
|
3077
|
+
};
|
|
3078
|
+
const selectConversationInputMode = async (target, raw) => {
|
|
3079
|
+
const mode = raw === 'next' ? 'next' : 'guide';
|
|
3080
|
+
const key = activeFlowStateKey(target);
|
|
3081
|
+
conversationSelections.set(key, { ...conversationSelections.get(key), inputMode: mode });
|
|
3082
|
+
if (mainConversationOwners.has(key))
|
|
3083
|
+
ensureConversationKernel(root).setInputMode(target, mode);
|
|
3084
|
+
else {
|
|
3085
|
+
const selected = wslBackendEnabled() ? await ensureWslConversationPool().setInputMode(target, mode) : await ensureElectronUtilityPool().setInputMode(target, mode);
|
|
3086
|
+
if (selected === null)
|
|
3087
|
+
isolatedConversationAgent(target).setInputMode(mode);
|
|
3088
|
+
}
|
|
3089
|
+
if (agent && activeFlowStateKey(conversationRuntimeTarget(agent.activeConversationId || 'default')) === key)
|
|
3090
|
+
agent.inputMode = mode;
|
|
3091
|
+
await publishConversationState(target);
|
|
3092
|
+
return mode;
|
|
3093
|
+
};
|
|
3094
|
+
const publishConversationState = async (target) => {
|
|
3095
|
+
const snapshot = await runtimeSnapshotForTarget(target);
|
|
3096
|
+
broadcastAgentWorkEvent({
|
|
3097
|
+
id: (0, crypto_1.randomUUID)(), type: 'queue_update', content: '', timestamp: new Date().toISOString(),
|
|
3098
|
+
workspaceId: target.workspaceId, conversationId: target.conversationId, mode: snapshot.mode,
|
|
3099
|
+
model: snapshot.model, inputMode: snapshot.inputMode, queue: snapshot.queued,
|
|
3100
|
+
queueItems: snapshot.queueItems, queuePaused: snapshot.queuePaused,
|
|
3101
|
+
});
|
|
3102
|
+
};
|
|
3103
|
+
const enqueueConversationGuide = async (envelope) => {
|
|
3104
|
+
const target = conversationRuntimeTarget(envelope.target);
|
|
3105
|
+
return mainConversationOwners.has(activeFlowStateKey(target))
|
|
3106
|
+
? ensureConversationKernel(root).enqueueGuide(envelope)
|
|
3107
|
+
: wslBackendEnabled() ? await ensureWslConversationPool().enqueueGuide(envelope) : await ensureElectronUtilityPool().enqueueGuide(envelope);
|
|
3108
|
+
};
|
|
3109
|
+
const mutateConversationQueue = async (target, rawAction, input = {}) => {
|
|
3110
|
+
const action = rawAction.replace(/^queue_/, '');
|
|
3111
|
+
if (!['enqueue', 'update', 'delete', 'reorder', 'toggle_pause', 'set_pause', 'guide'].includes(action))
|
|
3112
|
+
throw new Error('Unknown queue action');
|
|
3113
|
+
const key = activeFlowStateKey(target);
|
|
3114
|
+
const flow = activeFlowStateFor(target);
|
|
3115
|
+
const resumesQueue = action === 'set_pause' ? input.paused === false : action === 'toggle_pause' && flow;
|
|
3116
|
+
const pending = pendingFlowStarts.get(key);
|
|
3117
|
+
if (pending && (resumesQueue || action === 'toggle_pause')) {
|
|
3118
|
+
pending.cancelled = true;
|
|
3119
|
+
// Startup may be disposing the previous utility/WSL owner. Keep the
|
|
3120
|
+
// cancellation visible until that transfer settles before mutating it.
|
|
3121
|
+
await pending.settlement;
|
|
3122
|
+
}
|
|
3123
|
+
if (flow && resumesQueue) {
|
|
3124
|
+
if (flow.abortController) {
|
|
3125
|
+
await stopFlowForTarget(target);
|
|
3126
|
+
await flow.settlement;
|
|
3127
|
+
}
|
|
3128
|
+
await discardFlowSuspensionForTarget(target);
|
|
3129
|
+
ensureConversationKernel(root).releaseExternalRun(target, false);
|
|
3130
|
+
conversationSelections.set(key, { queuePaused: false });
|
|
3131
|
+
return { ok: true, ...await runtimeSnapshotForTarget(target) };
|
|
3132
|
+
}
|
|
3133
|
+
const result = mainConversationOwners.has(key)
|
|
3134
|
+
? ensureConversationKernel(root).queueAction(target, action, input)
|
|
3135
|
+
: wslBackendEnabled() ? await ensureWslConversationPool().queueAction(target, action, input) : await ensureElectronUtilityPool().queueAction(target, action, input);
|
|
3136
|
+
if (typeof result.queuePaused === 'boolean')
|
|
3137
|
+
conversationSelections.set(key, { queuePaused: result.queuePaused });
|
|
3138
|
+
return { ...result, ...await runtimeSnapshotForTarget(target) };
|
|
3139
|
+
};
|
|
3140
|
+
const applyConversationAction = async (requested, action, value = '', input = {}) => {
|
|
3141
|
+
const target = conversationRuntimeTarget(requested);
|
|
3142
|
+
if (action === 'conversation_branch_inspect')
|
|
3143
|
+
return await inspectConversationBranchForTarget(target, String(input.branchId || ''), String(input.branchGroupId || ''));
|
|
3144
|
+
if (action === 'conversation_branch_activate')
|
|
3145
|
+
return await activateConversationBranchForTarget(target, String(input.branchId || ''), String(input.branchGroupId || ''));
|
|
3146
|
+
if (action === 'conversation_branch_create')
|
|
3147
|
+
return await branchConversationForTarget(target, Number(input.messageIndex), String(input.editedText ?? input.text ?? value), {
|
|
3148
|
+
messageId: input.messageId, guideId: input.guideId, clientMessageId: input.clientMessageId,
|
|
3149
|
+
runId: input.runId, branchNodePath: input.branchNodePath,
|
|
3150
|
+
});
|
|
3151
|
+
if (action === 'conversation_archive')
|
|
3152
|
+
return await archiveConversationForTarget(target);
|
|
3153
|
+
if (action.startsWith('queue_'))
|
|
3154
|
+
return await mutateConversationQueue(target, action, input);
|
|
3155
|
+
if (action === 'mode')
|
|
3156
|
+
return { ok: true, mode: await selectConversationMode(target, value), ...await runtimeSnapshotForTarget(target) };
|
|
3157
|
+
if (action === 'goal_update') {
|
|
3158
|
+
const goal = mainConversationOwners.has(activeFlowStateKey(target))
|
|
3159
|
+
? ensureConversationKernel(root).updateGoal(target, String(value || '').trim())
|
|
3160
|
+
: await mutateTargetConversation(target, () => {
|
|
3161
|
+
const isolated = isolatedConversationAgent(target);
|
|
3162
|
+
isolated.updateGoal(String(value || '').trim());
|
|
3163
|
+
isolated.setMode('goal');
|
|
3164
|
+
isolated.saveWorkspaceConversationState(true);
|
|
3165
|
+
return isolated.getConversationSnapshot(target.conversationId).goal;
|
|
3166
|
+
});
|
|
3167
|
+
await selectConversationMode(target, 'goal');
|
|
3168
|
+
return { goal };
|
|
3169
|
+
}
|
|
3170
|
+
if (action === 'goal_guide' || action === 'conversation_guide') {
|
|
3171
|
+
const objective = action === 'goal_guide' ? String(value || '').trim() : '';
|
|
3172
|
+
const guideText = String(value || '').trim();
|
|
3173
|
+
const snapshot = await runtimeSnapshotForTarget(target);
|
|
3174
|
+
const runtime = snapshot.runtime;
|
|
3175
|
+
// `running` may turn false one IPC task before a finalization-
|
|
3176
|
+
// window Guide reaches the worker. Preserve the authoritative
|
|
3177
|
+
// runId and let ConversationKernel decide whether that run can
|
|
3178
|
+
// be reactivated or must reject a genuinely stale request.
|
|
3179
|
+
if (!guideText || !runtime?.runId) {
|
|
3180
|
+
return { ok: false, error: action === 'goal_guide'
|
|
3181
|
+
? 'There is no active Build for this Goal Guide.'
|
|
3182
|
+
: 'Target conversation is not running.' };
|
|
3183
|
+
}
|
|
3184
|
+
const prefix = 'Goal for the current Build:\n';
|
|
3185
|
+
const now = new Date().toISOString();
|
|
3186
|
+
const envelope = {
|
|
3187
|
+
clientMessageId: (0, crypto_1.randomUUID)(),
|
|
3188
|
+
guideId: (0, crypto_1.randomUUID)(),
|
|
3189
|
+
target: { workspaceId: target.workspaceId, conversationId: target.conversationId },
|
|
3190
|
+
runId: runtime.runId,
|
|
3191
|
+
deliveryMode: 'steer',
|
|
3192
|
+
text: objective ? prefix + objective : guideText,
|
|
3193
|
+
goalObjective: objective || undefined,
|
|
3194
|
+
createdAt: now,
|
|
3195
|
+
};
|
|
3196
|
+
const receipt = await enqueueConversationGuide(envelope);
|
|
3197
|
+
return { ok: receipt.status !== 'rejected', receipt };
|
|
3198
|
+
}
|
|
3199
|
+
if (action === 'goal_toggle_pause') {
|
|
3200
|
+
if (mainConversationOwners.has(activeFlowStateKey(target)))
|
|
3201
|
+
return { ok: true, paused: await ensureConversationKernel(root).toggleGoalPause(target) };
|
|
3202
|
+
const resident = wslBackendEnabled()
|
|
3203
|
+
? await ensureWslConversationPool().toggleGoalPause(target)
|
|
3204
|
+
: await ensureElectronUtilityPool().toggleGoalPause(target);
|
|
3205
|
+
const paused = resident !== null ? resident : await mutateTargetConversation(target, () => isolatedConversationAgent(target).toggleGoalPause());
|
|
3206
|
+
return { ok: true, paused };
|
|
3207
|
+
}
|
|
3208
|
+
if (action === 'goal_clear') {
|
|
3209
|
+
if (mainConversationOwners.has(activeFlowStateKey(target))) {
|
|
3210
|
+
const cleared = ensureConversationKernel(root).clearGoal(target);
|
|
3211
|
+
if (conversationSelections.get(activeFlowStateKey(target))?.mode === 'goal')
|
|
3212
|
+
await selectConversationMode(target, 'build');
|
|
3213
|
+
return { ok: true, cleared };
|
|
3214
|
+
}
|
|
3215
|
+
const resident = wslBackendEnabled()
|
|
3216
|
+
? await ensureWslConversationPool().clearGoal(target)
|
|
3217
|
+
: await ensureElectronUtilityPool().clearGoal(target);
|
|
3218
|
+
if (resident === null)
|
|
3219
|
+
await mutateTargetConversation(target, () => isolatedConversationAgent(target).clearGoal());
|
|
3220
|
+
if (conversationSelections.get(activeFlowStateKey(target))?.mode === 'goal')
|
|
3221
|
+
await selectConversationMode(target, 'build');
|
|
3222
|
+
return { ok: true, cleared: true };
|
|
3223
|
+
}
|
|
3224
|
+
if (action === 'flow_pause')
|
|
3225
|
+
return await stopFlowForTarget(target);
|
|
3226
|
+
if (action === 'flow_resume')
|
|
3227
|
+
return { ...await resumeFlowForTarget(String(value || ''), target), ...await runtimeSnapshotForTarget(target) };
|
|
3228
|
+
if (action === 'conversation_stop') {
|
|
3229
|
+
if (activeFlowStateFor(target) || pendingFlowStarts.has(activeFlowStateKey(target)))
|
|
3230
|
+
return await stopFlowForTarget(target);
|
|
3231
|
+
const snapshot = await runtimeSnapshotForTarget(target);
|
|
3232
|
+
const runId = snapshot.runtime?.runId;
|
|
3233
|
+
return mainConversationOwners.has(activeFlowStateKey(target))
|
|
3234
|
+
? { ...ensureConversationKernel(root).requestStop(target, runId) }
|
|
3235
|
+
: wslBackendEnabled() ? await ensureWslConversationPool().requestStop(target, runId) : await ensureElectronUtilityPool().requestStop(target, runId);
|
|
3236
|
+
}
|
|
3237
|
+
if (action === 'input_mode')
|
|
3238
|
+
return { ok: true, inputMode: await selectConversationInputMode(target, value) };
|
|
3239
|
+
const flow = activeFlowStateFor(target);
|
|
3240
|
+
if (!flow?.abortController || !flow.flowAgent)
|
|
3241
|
+
return { ok: false, error: 'No active Flow accepts Guide input.' };
|
|
3242
|
+
const accepted = flow.flowAgent.queueActiveKernelMessage(String(value || '').trim(), 'steer') || false;
|
|
3243
|
+
return accepted ? { ok: true, accepted: true, flow: flow.name } : { ok: false, error: 'The current Flow Build is not accepting Guide input.' };
|
|
3244
|
+
};
|
|
3245
|
+
const submitConversationCommand = async (message, targetInput, requested = {}) => {
|
|
3246
|
+
const target = conversationRuntimeTarget(targetInput);
|
|
3247
|
+
const id = String(requested.clientMessageId || (typeof message === 'string' ? '' : message.clientMessageId) || (0, crypto_1.randomUUID)());
|
|
3248
|
+
const key = activeFlowStateKey(target) + '::' + id;
|
|
3249
|
+
const accepted = conversationCommands.get(key);
|
|
3250
|
+
if (accepted)
|
|
3251
|
+
return { ...await accepted, ...await runtimeSnapshotForTarget(target) };
|
|
3252
|
+
const work = executeConversationCommand(message, target, { ...requested, clientMessageId: id });
|
|
3253
|
+
conversationCommands.set(key, work);
|
|
3254
|
+
// Retain settled ids to reject ambiguous transport replays without
|
|
3255
|
+
// retaining an unbounded history or deduplicating different user inputs.
|
|
3256
|
+
void work.then(result => {
|
|
3257
|
+
conversationCommands.set(key, Promise.resolve({
|
|
3258
|
+
...(result.error ? { error: result.error } : { ok: true }), duplicate: true,
|
|
3259
|
+
workspaceId: target.workspaceId, conversationId: target.conversationId,
|
|
3260
|
+
}));
|
|
3261
|
+
if (conversationCommands.size > 1000)
|
|
3262
|
+
conversationCommands.delete(conversationCommands.keys().next().value);
|
|
3263
|
+
}).catch(() => { });
|
|
3264
|
+
return await work;
|
|
3265
|
+
};
|
|
3266
|
+
const executeConversationCommand = async (message, targetInput, requested = {}) => {
|
|
3146
3267
|
if (!agent)
|
|
3147
3268
|
return { tokens: [], error: 'Agent not initialized' };
|
|
3148
3269
|
let promptLeaseKey = '';
|
|
@@ -3154,8 +3275,42 @@ else {
|
|
|
3154
3275
|
// conversation into the user's new process (Flow pause state exit rule).
|
|
3155
3276
|
// flow:run already discards the suspension for a new Flow workflow.
|
|
3156
3277
|
// Other conversations' paused Flows are never touched by this send.
|
|
3157
|
-
|
|
3158
|
-
|
|
3278
|
+
const flow = activeFlowStateFor(target);
|
|
3279
|
+
const snapshot = await runtimeSnapshotForTarget(target);
|
|
3280
|
+
const structured = typeof message === 'string' ? { text: message } : { ...message };
|
|
3281
|
+
const requestedMode = String(requested.requestedMode || structured.visibleMode || snapshot.mode || 'build');
|
|
3282
|
+
const inputMode = String(requested.inputMode || snapshot.inputMode || 'guide') === 'next' ? 'next' : 'guide';
|
|
3283
|
+
const objective = String(requested.goalObjective ?? structured.goalObjective ?? (requestedMode === 'goal' ? structured.text : '')).trim();
|
|
3284
|
+
const id = String(requested.clientMessageId || structured.clientMessageId || (0, crypto_1.randomUUID)());
|
|
3285
|
+
message = { ...structured, clientMessageId: id, userMessageId: id, visibleMode: requestedMode, goalObjective: objective || undefined };
|
|
3286
|
+
const runtime = snapshot.runtime;
|
|
3287
|
+
if (flow?.reason === 'interrupted') {
|
|
3288
|
+
await discardFlowSuspensionForTarget(target);
|
|
3289
|
+
ensureConversationKernel(root).releaseExternalRun(target, true);
|
|
3290
|
+
}
|
|
3291
|
+
if (requestedMode === 'flow' && !activeFlowStateFor(target) && !pendingFlowStarts.has(activeFlowStateKey(target))) {
|
|
3292
|
+
if (inputMode === 'guide' && runtime?.running)
|
|
3293
|
+
return { ok: false, error: 'Flow Guide cannot take over a running Build. Use Next to start after it finishes.' };
|
|
3294
|
+
const selectedFlow = snapshot.flowSelection;
|
|
3295
|
+
const name = String(requested.flowName || selectedFlow?.name || agent.flow?.name || agent.config.getStr('flow', 'default_flow') || '');
|
|
3296
|
+
return { ...await runFlowForTarget(name, structured.text, requested.flowStart || 0, target), ...await runtimeSnapshotForTarget(target) };
|
|
3297
|
+
}
|
|
3298
|
+
if (flow?.reason !== 'interrupted' && (flow || pendingFlowStarts.has(activeFlowStateKey(target)) || runtime?.running || snapshot.queuePaused)) {
|
|
3299
|
+
if (inputMode === 'guide' && (runtime?.running || flow?.abortController)) {
|
|
3300
|
+
const receipt = await enqueueConversationGuide({
|
|
3301
|
+
clientMessageId: id, guideId: structured.guideId || (0, crypto_1.randomUUID)(), target,
|
|
3302
|
+
runId: runtime?.runId || '', deliveryMode: 'steer', text: objective ? 'Goal for the current Build:\n' + objective : structured.text,
|
|
3303
|
+
goalObjective: objective || undefined, images: structured.images, createdAt: new Date().toISOString(),
|
|
3304
|
+
});
|
|
3305
|
+
return { ok: receipt.status !== 'rejected', accepted: receipt.status !== 'rejected', receipt, ...await runtimeSnapshotForTarget(target) };
|
|
3306
|
+
}
|
|
3307
|
+
return await applyConversationAction(target, 'queue_enqueue', '', {
|
|
3308
|
+
id, text: structured.visibleUserInput || structured.text, requestedMode: requestedMode === 'flow' ? 'build' : requestedMode,
|
|
3309
|
+
goalObjective: objective, images: structured.images, createdAt: new Date().toISOString(),
|
|
3310
|
+
});
|
|
3311
|
+
}
|
|
3312
|
+
await selectConversationMode(target, requestedMode);
|
|
3313
|
+
await selectConversationInputMode(target, inputMode);
|
|
3159
3314
|
const normalizedPromptTarget = (0, conversationTarget_1.normalizeConversationTarget)(target);
|
|
3160
3315
|
promptLeaseKey = normalizedPromptTarget.runtimeKey;
|
|
3161
3316
|
promptLeaseWorkspaceKey = normalizedPromptTarget.workspaceKey;
|
|
@@ -3165,12 +3320,12 @@ else {
|
|
|
3165
3320
|
// 发送命令时锁定输入框选择的模型:接受命令后的整个运行过程都以该
|
|
3166
3321
|
// 模型为准(唯一例外是显式的不可用回退,且回退会以结构化事件同步
|
|
3167
3322
|
// 到前端输入框下方的选择区,而不是隐藏的参数回退)。
|
|
3168
|
-
const requestedModel = agent.model;
|
|
3323
|
+
const requestedModel = String(snapshot.model || agent.model);
|
|
3169
3324
|
const options = {
|
|
3170
|
-
mode:
|
|
3171
|
-
model: agent.ensureUsableModelSelection(),
|
|
3172
|
-
intelligence: agent.intelligence,
|
|
3173
|
-
inputMode:
|
|
3325
|
+
mode: requestedMode === 'goal' ? 'goal' : requestedMode,
|
|
3326
|
+
model: String(snapshot.model || agent.ensureUsableModelSelection()),
|
|
3327
|
+
intelligence: String(snapshot.intelligence || agent.intelligence),
|
|
3328
|
+
inputMode: inputMode,
|
|
3174
3329
|
engine: agent.engine,
|
|
3175
3330
|
};
|
|
3176
3331
|
if (options.model && requestedModel && options.model !== requestedModel) {
|
|
@@ -3188,9 +3343,12 @@ else {
|
|
|
3188
3343
|
fallback: { from: requestedModel, to: options.model, providerId: usableConfig?.provider_id || agent.activeDeployment()?.providerId },
|
|
3189
3344
|
});
|
|
3190
3345
|
}
|
|
3191
|
-
const queueMode =
|
|
3346
|
+
const queueMode = inputMode === 'guide' ? 'steer' : 'followUp';
|
|
3192
3347
|
let result;
|
|
3193
|
-
if (
|
|
3348
|
+
if (mainConversationOwners.has(activeFlowStateKey(target))) {
|
|
3349
|
+
result = await ensureConversationKernel(root).prompt(message, target, options, queueMode);
|
|
3350
|
+
}
|
|
3351
|
+
else if (wslBackendEnabled()) {
|
|
3194
3352
|
const pool = ensureWslConversationPool();
|
|
3195
3353
|
if (!pool)
|
|
3196
3354
|
return { error: 'WSL Agent backend is enabled but unavailable.' };
|
|
@@ -3220,6 +3378,7 @@ else {
|
|
|
3220
3378
|
// target-scoped snapshot consumed by the renderer.
|
|
3221
3379
|
return {
|
|
3222
3380
|
...result,
|
|
3381
|
+
...await runtimeSnapshotForTarget(target),
|
|
3223
3382
|
conversationId: targetConversation,
|
|
3224
3383
|
activeConversationId: agent.activeConversationId || previousConversation,
|
|
3225
3384
|
conversationLocked: false,
|
|
@@ -3245,7 +3404,8 @@ else {
|
|
|
3245
3404
|
activePromptWorkspaces.delete(promptLeaseWorkspaceKey);
|
|
3246
3405
|
}
|
|
3247
3406
|
}
|
|
3248
|
-
}
|
|
3407
|
+
};
|
|
3408
|
+
electron_1.ipcMain.handle('agent:send', async (_event, message, target, options) => await submitConversationCommand(message, target, options));
|
|
3249
3409
|
electron_1.ipcMain.handle('browser:registerGuest', (event, guestContentsId, target) => {
|
|
3250
3410
|
const guest = electron_1.webContents.fromId(Number(guestContentsId || 0));
|
|
3251
3411
|
return { accepted: !!guest && registerBrowserGuest(event.sender, guest, target) };
|
|
@@ -3253,7 +3413,7 @@ else {
|
|
|
3253
3413
|
electron_1.ipcMain.handle('browser:control', async (_event, request) => {
|
|
3254
3414
|
return await browserControl_1.BrowserControl.run(request);
|
|
3255
3415
|
});
|
|
3256
|
-
|
|
3416
|
+
const runFlowForTarget = async (name, input = '', start = 0, requestedTarget) => {
|
|
3257
3417
|
if (!agent)
|
|
3258
3418
|
return { ok: false, error: 'Agent not initialized' };
|
|
3259
3419
|
// Bind the Flow to the conversation that started it, exactly like resume.
|
|
@@ -3261,7 +3421,7 @@ else {
|
|
|
3261
3421
|
// to its owning conversation id and branch, so switching conversations
|
|
3262
3422
|
// mid-run can never make the next component write into another
|
|
3263
3423
|
// conversation or strand the running flag.
|
|
3264
|
-
const flowTarget = conversationRuntimeTarget(agent.activeConversationId || 'default');
|
|
3424
|
+
const flowTarget = conversationRuntimeTarget(requestedTarget || agent.activeConversationId || 'default');
|
|
3265
3425
|
const flowKey = activeFlowStateKey(flowTarget);
|
|
3266
3426
|
const existing = activeFlowsByRuntimeKey.get(flowKey) || null;
|
|
3267
3427
|
if (existing && (existing.reason === 'question' || existing.reason === 'interrupted')) {
|
|
@@ -3277,12 +3437,45 @@ else {
|
|
|
3277
3437
|
const workflow = flow_1.FlowEngine.load(flowDir, found);
|
|
3278
3438
|
if (!workflow)
|
|
3279
3439
|
return { ok: false, error: `Workflow failed to load: ${found}` };
|
|
3280
|
-
|
|
3440
|
+
if (pendingFlowStarts.has(flowKey))
|
|
3441
|
+
return { ok: false, error: 'A Flow is already waiting for this conversation.' };
|
|
3442
|
+
let resolvePendingSettlement;
|
|
3443
|
+
const pending = {
|
|
3444
|
+
name: workflow.name, input: String(input || ''), cancelled: false,
|
|
3445
|
+
settlement: new Promise(resolve => { resolvePendingSettlement = resolve; }),
|
|
3446
|
+
};
|
|
3447
|
+
pendingFlowStarts.set(flowKey, pending);
|
|
3448
|
+
let previousQueuePaused = false;
|
|
3449
|
+
try {
|
|
3450
|
+
let state = await runtimeSnapshotForTarget(flowTarget);
|
|
3451
|
+
previousQueuePaused = state.queuePaused === true;
|
|
3452
|
+
await mutateConversationQueue(flowTarget, 'queue_set_pause', { paused: true });
|
|
3453
|
+
while (state.runtime?.running) {
|
|
3454
|
+
if (pending.cancelled)
|
|
3455
|
+
return { ok: false, interrupted: true, error: 'Flow start cancelled.' };
|
|
3456
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
3457
|
+
state = await runtimeSnapshotForTarget(flowTarget);
|
|
3458
|
+
}
|
|
3459
|
+
if (pending.cancelled)
|
|
3460
|
+
return { ok: false, interrupted: true, error: 'Flow start cancelled.' };
|
|
3461
|
+
if (!mainConversationOwners.has(flowKey))
|
|
3462
|
+
await stopTargetRuntime(flowTarget);
|
|
3463
|
+
if (pending.cancelled)
|
|
3464
|
+
return { ok: false, interrupted: true, error: 'Flow start cancelled.' };
|
|
3465
|
+
}
|
|
3466
|
+
finally {
|
|
3467
|
+
if (pendingFlowStarts.get(flowKey) === pending)
|
|
3468
|
+
pendingFlowStarts.delete(flowKey);
|
|
3469
|
+
resolvePendingSettlement();
|
|
3470
|
+
}
|
|
3471
|
+
const flowAgent = ensureConversationKernel(root).beginExternalRun(flowTarget, {
|
|
3472
|
+
mode: 'flow', model: agent.model, intelligence: agent.intelligence, inputMode: agent.inputMode, engine: agent.engine,
|
|
3473
|
+
}, () => isolatedConversationAgent(flowTarget), previousQueuePaused);
|
|
3474
|
+
mainConversationOwners.add(flowKey);
|
|
3475
|
+
conversationSelections.set(flowKey, { ...conversationSelections.get(flowKey), mode: 'flow' });
|
|
3281
3476
|
const previousMode = flowAgent.mode;
|
|
3282
3477
|
const previousFlow = flowAgent.flow;
|
|
3283
3478
|
const previousPc = flowAgent.flowPc;
|
|
3284
|
-
if (previousMode === 'plan')
|
|
3285
|
-
return { ok: false, error: 'Plan mode is fully read-only; Flow execution is blocked.' };
|
|
3286
3479
|
const flowAbortController = new AbortController();
|
|
3287
3480
|
const flowState = {
|
|
3288
3481
|
workflow,
|
|
@@ -3290,6 +3483,7 @@ else {
|
|
|
3290
3483
|
componentId: 0,
|
|
3291
3484
|
completedResults: [],
|
|
3292
3485
|
previousMode,
|
|
3486
|
+
queueWasPaused: previousQueuePaused,
|
|
3293
3487
|
previousFlow,
|
|
3294
3488
|
previousPc,
|
|
3295
3489
|
reason: '',
|
|
@@ -3299,7 +3493,10 @@ else {
|
|
|
3299
3493
|
flowAgent,
|
|
3300
3494
|
};
|
|
3301
3495
|
activeFlowsByRuntimeKey.set(flowKey, flowState);
|
|
3302
|
-
|
|
3496
|
+
let resolveSettlement;
|
|
3497
|
+
flowState.settlement = new Promise(resolve => { resolveSettlement = resolve; });
|
|
3498
|
+
void publishConversationState(flowTarget).catch(console.error);
|
|
3499
|
+
const stopRelayingFlowEvents = () => { }; // The shared kernel already relays this exact owner.
|
|
3303
3500
|
let suspended = false;
|
|
3304
3501
|
try {
|
|
3305
3502
|
flowAgent.flow = workflow;
|
|
@@ -3347,7 +3544,7 @@ else {
|
|
|
3347
3544
|
flowState.reason = 'question';
|
|
3348
3545
|
flowState.message = '';
|
|
3349
3546
|
flowState.abortController = null;
|
|
3350
|
-
|
|
3547
|
+
flowAgent.saveStoredFlowSuspension(persistedFlowSuspensionRecord(flowState), flowTarget.conversationId);
|
|
3351
3548
|
return {
|
|
3352
3549
|
ok: true,
|
|
3353
3550
|
pending: true,
|
|
@@ -3383,7 +3580,7 @@ else {
|
|
|
3383
3580
|
flowState.message = interruptedMessage;
|
|
3384
3581
|
flowState.abortController = null;
|
|
3385
3582
|
flowAgent.pendingOptions = [];
|
|
3386
|
-
|
|
3583
|
+
flowAgent.saveStoredFlowSuspension(persistedFlowSuspensionRecord(flowState), flowTarget.conversationId);
|
|
3387
3584
|
return {
|
|
3388
3585
|
ok: true,
|
|
3389
3586
|
pending: true,
|
|
@@ -3403,9 +3600,19 @@ else {
|
|
|
3403
3600
|
flowAgent.flowPc = previousPc;
|
|
3404
3601
|
flowAgent.setMode(previousMode);
|
|
3405
3602
|
activeFlowsByRuntimeKey.delete(flowKey);
|
|
3603
|
+
if (!flowState.archiveRequested)
|
|
3604
|
+
await setTargetFlowMode(flowTarget, previousMode, true);
|
|
3605
|
+
}
|
|
3606
|
+
try {
|
|
3607
|
+
ensureConversationKernel(root).settleExternalRun(flowTarget, !suspended);
|
|
3608
|
+
conversationSelections.set(flowKey, { queuePaused: ensureConversationKernel(root).snapshot(flowTarget).queuePaused });
|
|
3609
|
+
}
|
|
3610
|
+
finally {
|
|
3611
|
+
resolveSettlement();
|
|
3406
3612
|
}
|
|
3407
3613
|
}
|
|
3408
|
-
}
|
|
3614
|
+
};
|
|
3615
|
+
electron_1.ipcMain.handle('flow:run', async (_event, name, input, start, target) => await runFlowForTarget(name, input, start, target));
|
|
3409
3616
|
const resumeFlowForTarget = async (response, targetInput) => {
|
|
3410
3617
|
if (!agent)
|
|
3411
3618
|
return { ok: false, error: 'No suspended Flow is waiting for user input.' };
|
|
@@ -3428,18 +3635,33 @@ else {
|
|
|
3428
3635
|
}
|
|
3429
3636
|
const flowTarget = suspension.target;
|
|
3430
3637
|
const flowKey = activeFlowStateKey(flowTarget);
|
|
3431
|
-
|
|
3638
|
+
if (!mainConversationOwners.has(flowKey))
|
|
3639
|
+
await stopTargetRuntime(flowTarget);
|
|
3640
|
+
const flowAgent = ensureConversationKernel(root).beginExternalRun(flowTarget, {
|
|
3641
|
+
mode: 'flow', model: suspension.flowAgent?.model || agent.model, intelligence: suspension.flowAgent?.intelligence || agent.intelligence,
|
|
3642
|
+
inputMode: suspension.flowAgent?.inputMode || agent.inputMode, engine: agent.engine,
|
|
3643
|
+
}, () => suspension.flowAgent || isolatedConversationAgent(flowTarget), suspension.queueWasPaused ?? true);
|
|
3644
|
+
mainConversationOwners.add(flowKey);
|
|
3432
3645
|
suspension.flowAgent = flowAgent;
|
|
3433
3646
|
// A previous Flow may have been interrupted just before its isolated
|
|
3434
3647
|
// Agent flushed the work ledger. Reconcile only this explicitly paused
|
|
3435
3648
|
// target before starting the next component; the normal runner guard
|
|
3436
3649
|
// remains intact for genuine concurrent Builds.
|
|
3437
3650
|
flowAgent.interruptRunningConversationWorkRuns(flowTarget, 'interrupted');
|
|
3651
|
+
const resumeReason = suspension.reason;
|
|
3438
3652
|
const flowAbortController = new AbortController();
|
|
3653
|
+
// The pause reason belongs to the previous component wait. Once this
|
|
3654
|
+
// owner resumes, mobile projection and the next Stop must see a running
|
|
3655
|
+
// Flow, while the captured reason retains question-answer semantics.
|
|
3656
|
+
suspension.reason = '';
|
|
3657
|
+
suspension.message = '';
|
|
3439
3658
|
suspension.abortController = flowAbortController;
|
|
3440
3659
|
activeFlowsByRuntimeKey.set(flowKey, suspension);
|
|
3441
|
-
|
|
3442
|
-
|
|
3660
|
+
flowAgent.clearStoredFlowSuspension(flowTarget.conversationId);
|
|
3661
|
+
let resolveSettlement;
|
|
3662
|
+
suspension.settlement = new Promise(resolve => { resolveSettlement = resolve; });
|
|
3663
|
+
void publishConversationState(flowTarget).catch(console.error);
|
|
3664
|
+
const stopRelayingFlowEvents = () => { };
|
|
3443
3665
|
let suspendedAgain = false;
|
|
3444
3666
|
try {
|
|
3445
3667
|
flowAgent.flow = suspension.workflow;
|
|
@@ -3448,7 +3670,7 @@ else {
|
|
|
3448
3670
|
await (0, flow_runner_1.runFlow)(flowAgent, suspension.workflow, {
|
|
3449
3671
|
startInput: suspension.input,
|
|
3450
3672
|
startPc: suspension.componentId,
|
|
3451
|
-
resumePrompt:
|
|
3673
|
+
resumePrompt: resumeReason === 'interrupted' ? '' : String(response || ''),
|
|
3452
3674
|
completedResults: suspension.completedResults,
|
|
3453
3675
|
quiet: true,
|
|
3454
3676
|
signal: flowAbortController.signal,
|
|
@@ -3484,7 +3706,7 @@ else {
|
|
|
3484
3706
|
suspension.reason = 'question';
|
|
3485
3707
|
suspension.message = '';
|
|
3486
3708
|
suspension.abortController = null;
|
|
3487
|
-
|
|
3709
|
+
flowAgent.saveStoredFlowSuspension(persistedFlowSuspensionRecord(suspension), flowTarget.conversationId);
|
|
3488
3710
|
return {
|
|
3489
3711
|
ok: true,
|
|
3490
3712
|
pending: true,
|
|
@@ -3517,7 +3739,7 @@ else {
|
|
|
3517
3739
|
suspension.message = interruptedMessage;
|
|
3518
3740
|
suspension.abortController = null;
|
|
3519
3741
|
flowAgent.pendingOptions = [];
|
|
3520
|
-
|
|
3742
|
+
flowAgent.saveStoredFlowSuspension(persistedFlowSuspensionRecord(suspension), flowTarget.conversationId);
|
|
3521
3743
|
return {
|
|
3522
3744
|
ok: true,
|
|
3523
3745
|
pending: true,
|
|
@@ -3537,27 +3759,19 @@ else {
|
|
|
3537
3759
|
flowAgent.flowPc = suspension.previousPc;
|
|
3538
3760
|
flowAgent.setMode(suspension.previousMode);
|
|
3539
3761
|
activeFlowsByRuntimeKey.delete(flowKey);
|
|
3540
|
-
await setTargetFlowMode(flowTarget, suspension.previousMode);
|
|
3762
|
+
await setTargetFlowMode(flowTarget, suspension.previousMode, true);
|
|
3763
|
+
}
|
|
3764
|
+
try {
|
|
3765
|
+
ensureConversationKernel(root).settleExternalRun(flowTarget, !suspendedAgain);
|
|
3766
|
+
conversationSelections.set(flowKey, { queuePaused: ensureConversationKernel(root).snapshot(flowTarget).queuePaused });
|
|
3767
|
+
}
|
|
3768
|
+
finally {
|
|
3769
|
+
resolveSettlement();
|
|
3541
3770
|
}
|
|
3542
3771
|
}
|
|
3543
3772
|
};
|
|
3544
3773
|
electron_1.ipcMain.handle('flow:resume', async (_event, response, targetInput) => await resumeFlowForTarget(response, targetInput));
|
|
3545
|
-
electron_1.ipcMain.handle('agent:setMode', async (_event, mode) =>
|
|
3546
|
-
if (agent) {
|
|
3547
|
-
const nextMode = mode;
|
|
3548
|
-
agent.setMode(nextMode);
|
|
3549
|
-
const target = conversationRuntimeTarget(agent.activeConversationId || 'default');
|
|
3550
|
-
// The target-bound runner owns conversation persistence. Updating only
|
|
3551
|
-
// the host Agent lets an existing/cold runner restore the stale Build
|
|
3552
|
-
// snapshot and silently erase Plan/Goal/Flow on the next send.
|
|
3553
|
-
ensureConversationKernel(root)?.setMode(target, nextMode);
|
|
3554
|
-
if (wslBackendEnabled())
|
|
3555
|
-
await ensureWslConversationPool().setMode(target, nextMode);
|
|
3556
|
-
else
|
|
3557
|
-
await ensureElectronUtilityPool().setMode(target, nextMode);
|
|
3558
|
-
}
|
|
3559
|
-
return agent?.mode;
|
|
3560
|
-
});
|
|
3774
|
+
electron_1.ipcMain.handle('agent:setMode', async (_event, mode, target) => await selectConversationMode(conversationRuntimeTarget(target), mode));
|
|
3561
3775
|
electron_1.ipcMain.handle('agent:setModel', async (_event, model) => {
|
|
3562
3776
|
if (agent) {
|
|
3563
3777
|
// Compare the resolved selection (qualified deployment or 'auto')
|
|
@@ -3613,23 +3827,7 @@ else {
|
|
|
3613
3827
|
models: agent.allModelNames(),
|
|
3614
3828
|
};
|
|
3615
3829
|
});
|
|
3616
|
-
electron_1.ipcMain.handle('agent:setInputMode', async (_event, mode,
|
|
3617
|
-
if (!agent)
|
|
3618
|
-
return mode === 'next' ? 'next' : 'guide';
|
|
3619
|
-
const target = (0, conversationTarget_1.normalizeConversationTarget)(conversationRuntimeTarget(targetInput));
|
|
3620
|
-
const current = (0, conversationTarget_1.normalizeConversationTarget)(conversationRuntimeTarget({
|
|
3621
|
-
workspaceId: agent.workspace.current?.id || '',
|
|
3622
|
-
conversationId: agent.activeConversationId,
|
|
3623
|
-
}));
|
|
3624
|
-
const persisted = target.runtimeKey === current.runtimeKey
|
|
3625
|
-
? agent.setInputMode(mode)
|
|
3626
|
-
: isolatedConversationAgent(target).setInputMode(mode);
|
|
3627
|
-
if (wslBackendEnabled())
|
|
3628
|
-
await ensureWslConversationPool().setInputMode(target, persisted);
|
|
3629
|
-
else
|
|
3630
|
-
await ensureElectronUtilityPool().setInputMode(target, persisted);
|
|
3631
|
-
return persisted;
|
|
3632
|
-
});
|
|
3830
|
+
electron_1.ipcMain.handle('agent:setInputMode', async (_event, mode, target) => await selectConversationInputMode(conversationRuntimeTarget(target), mode));
|
|
3633
3831
|
electron_1.ipcMain.handle('agent:setConversation', async (_event, id) => {
|
|
3634
3832
|
return agent?.setConversationFromStorage(id);
|
|
3635
3833
|
});
|
|
@@ -3637,7 +3835,12 @@ else {
|
|
|
3637
3835
|
if (!agent)
|
|
3638
3836
|
return {};
|
|
3639
3837
|
const target = conversationRuntimeTarget(targetInput || agent.activeConversationId || 'default');
|
|
3640
|
-
|
|
3838
|
+
const workspace = (target.workspace || agent.workspace.current);
|
|
3839
|
+
const existed = agent.hasConversationInWorkspace(target.conversationId, workspace);
|
|
3840
|
+
const snapshot = await runtimeSnapshotForTarget(target);
|
|
3841
|
+
if (!existed)
|
|
3842
|
+
publishConversationList(workspace);
|
|
3843
|
+
return snapshot;
|
|
3641
3844
|
});
|
|
3642
3845
|
electron_1.ipcMain.handle('agent:activateConversation', async (_event, targetInput) => {
|
|
3643
3846
|
if (!agent)
|
|
@@ -3653,24 +3856,31 @@ else {
|
|
|
3653
3856
|
icon: '',
|
|
3654
3857
|
kind: target.workspace.kind === 'ssh' ? 'ssh' : 'local',
|
|
3655
3858
|
} : agent.workspace.current;
|
|
3859
|
+
const existed = agent.hasConversationInWorkspace(target.conversationId, workspace);
|
|
3656
3860
|
agent.persistActiveConversationSelection(target.conversationId, workspace);
|
|
3657
3861
|
const resident = peekTargetRuntime(target).resident;
|
|
3658
3862
|
if (!resident) {
|
|
3659
|
-
|
|
3863
|
+
const snapshot = {
|
|
3660
3864
|
...localConversationSnapshotForStartup(target),
|
|
3661
3865
|
runtimeDeferred: true,
|
|
3662
3866
|
};
|
|
3867
|
+
if (!existed)
|
|
3868
|
+
publishConversationList(workspace);
|
|
3869
|
+
return snapshot;
|
|
3663
3870
|
}
|
|
3664
|
-
|
|
3871
|
+
const snapshot = {
|
|
3665
3872
|
...await runtimeSnapshotForTarget(target),
|
|
3666
3873
|
runtimeDeferred: false,
|
|
3667
3874
|
};
|
|
3875
|
+
if (!existed)
|
|
3876
|
+
publishConversationList(workspace);
|
|
3877
|
+
return snapshot;
|
|
3668
3878
|
});
|
|
3669
3879
|
electron_1.ipcMain.handle('agent:setConversationBranchCommunication', async (_event, targetInput, enabled) => {
|
|
3670
3880
|
if (!agent)
|
|
3671
3881
|
return false;
|
|
3672
3882
|
const target = conversationRuntimeTarget(targetInput || agent.activeConversationId || 'default');
|
|
3673
|
-
return mutateTargetConversation(target, () =>
|
|
3883
|
+
return mutateTargetConversation(target, () => conversationAgentForTarget(target).setBranchCommunication(enabled !== false));
|
|
3674
3884
|
});
|
|
3675
3885
|
electron_1.ipcMain.handle('agent:computerUseState', async (_event, targetInput) => {
|
|
3676
3886
|
if (!agent)
|
|
@@ -3698,38 +3908,19 @@ else {
|
|
|
3698
3908
|
if (!agent)
|
|
3699
3909
|
return null;
|
|
3700
3910
|
const target = conversationRuntimeTarget(targetInput || agent.activeConversationId || 'default');
|
|
3701
|
-
return
|
|
3702
|
-
const isolated = isolatedConversationAgent(target);
|
|
3703
|
-
isolated.updateGoal(goal);
|
|
3704
|
-
isolated.setMode('goal');
|
|
3705
|
-
isolated.saveWorkspaceConversationState(true);
|
|
3706
|
-
return isolated.getConversationSnapshot(target.conversationId).goal;
|
|
3707
|
-
});
|
|
3911
|
+
return (await applyConversationAction(target, 'goal_update', goal)).goal;
|
|
3708
3912
|
});
|
|
3709
3913
|
electron_1.ipcMain.handle('agent:toggleGoalPause', async (_event, targetInput) => {
|
|
3710
3914
|
if (!agent)
|
|
3711
3915
|
return false;
|
|
3712
3916
|
const target = conversationRuntimeTarget(targetInput || agent.activeConversationId || 'default');
|
|
3713
|
-
|
|
3714
|
-
? await ensureWslConversationPool().toggleGoalPause(target)
|
|
3715
|
-
: await ensureElectronUtilityPool().toggleGoalPause(target);
|
|
3716
|
-
if (residentResult !== null)
|
|
3717
|
-
return residentResult;
|
|
3718
|
-
return mutateTargetConversation(target, () => isolatedConversationAgent(target).toggleGoalPause());
|
|
3917
|
+
return (await applyConversationAction(target, 'goal_toggle_pause')).paused;
|
|
3719
3918
|
});
|
|
3720
3919
|
electron_1.ipcMain.handle('agent:clearGoal', async (_event, targetInput) => {
|
|
3721
3920
|
if (!agent)
|
|
3722
3921
|
return false;
|
|
3723
3922
|
const target = conversationRuntimeTarget(targetInput || agent.activeConversationId || 'default');
|
|
3724
|
-
|
|
3725
|
-
? await ensureWslConversationPool().clearGoal(target)
|
|
3726
|
-
: await ensureElectronUtilityPool().clearGoal(target);
|
|
3727
|
-
if (residentResult !== null)
|
|
3728
|
-
return residentResult;
|
|
3729
|
-
return mutateTargetConversation(target, () => {
|
|
3730
|
-
isolatedConversationAgent(target).clearGoal();
|
|
3731
|
-
return true;
|
|
3732
|
-
});
|
|
3923
|
+
return (await applyConversationAction(target, 'goal_clear')).cleared;
|
|
3733
3924
|
});
|
|
3734
3925
|
electron_1.ipcMain.handle('agent:getState', async (event, targetInput) => {
|
|
3735
3926
|
const startupPrewarmRequest = isStartupPrewarmSender(event);
|
|
@@ -3747,14 +3938,12 @@ else {
|
|
|
3747
3938
|
? targetInput
|
|
3748
3939
|
: {});
|
|
3749
3940
|
const requestedWindow = Math.max(1, Math.floor(Number(windowInput.window) || 0) || 200);
|
|
3750
|
-
const requestedBefore = Math.max(0, Math.floor(Number(windowInput.before) || 0));
|
|
3941
|
+
const requestedBefore = windowInput.before == null ? undefined : Math.max(0, Math.floor(Number(windowInput.before) || 0));
|
|
3751
3942
|
let conversationSnapshot;
|
|
3752
3943
|
try {
|
|
3753
3944
|
conversationSnapshot = startupPrewarmRequest
|
|
3754
|
-
? localConversationSnapshotForStartup(target)
|
|
3755
|
-
:
|
|
3756
|
-
? await ensureWslConversationPool().snapshot(target)
|
|
3757
|
-
: await ensureElectronUtilityPool().snapshot(target);
|
|
3945
|
+
? localConversationSnapshotForStartup(target, { window: requestedWindow, before: requestedBefore })
|
|
3946
|
+
: await runtimeSnapshotForTarget(target, { window: requestedWindow, before: requestedBefore });
|
|
3758
3947
|
}
|
|
3759
3948
|
catch (error) {
|
|
3760
3949
|
conversationSnapshot = {
|
|
@@ -3768,11 +3957,8 @@ else {
|
|
|
3768
3957
|
workRuns: [],
|
|
3769
3958
|
};
|
|
3770
3959
|
}
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
const before = requestedBefore > 0 ? Math.min(totalMessages, requestedBefore) : totalMessages;
|
|
3774
|
-
const windowStart = Math.max(0, before - requestedWindow);
|
|
3775
|
-
const windowedMessages = fullMessages.slice(windowStart, before);
|
|
3960
|
+
// The owner already applied this window to its complete live history.
|
|
3961
|
+
// Re-slicing here would lose the absolute cursor and repeat recent rows.
|
|
3776
3962
|
return {
|
|
3777
3963
|
mode: agent.mode,
|
|
3778
3964
|
model: agent.modelSelectionValue(),
|
|
@@ -3781,9 +3967,9 @@ else {
|
|
|
3781
3967
|
routeDecision: agent.lastRouteDecision,
|
|
3782
3968
|
intelligence: agent.intelligence,
|
|
3783
3969
|
...conversationSnapshot,
|
|
3784
|
-
chatMessages:
|
|
3785
|
-
totalMessages,
|
|
3786
|
-
windowStart,
|
|
3970
|
+
chatMessages: conversationSnapshot.chatMessages || [],
|
|
3971
|
+
totalMessages: conversationSnapshot.totalMessages || 0,
|
|
3972
|
+
windowStart: conversationSnapshot.windowStart || 0,
|
|
3787
3973
|
conversationLocked: conversationSnapshot.conversationLocked ?? false,
|
|
3788
3974
|
status: conversationSnapshot.status ?? 'idle',
|
|
3789
3975
|
goal: conversationSnapshot.goal ?? null,
|
|
@@ -3822,7 +4008,7 @@ else {
|
|
|
3822
4008
|
fallbackOnUnavailable: agent.config.getBool('models', 'fallback_on_unavailable'),
|
|
3823
4009
|
openAIApiMode: agent.config.openAIApiMode(),
|
|
3824
4010
|
autoAdjust: agent.config.getBool('agent', 'auto_adjust_settings'),
|
|
3825
|
-
inputMode: agent.inputMode,
|
|
4011
|
+
inputMode: conversationSnapshot.inputMode || agent.inputMode,
|
|
3826
4012
|
terminalInterruptTimeoutMs: agent.config.getNum('terminal', 'interrupt_timeout_ms'),
|
|
3827
4013
|
platform: process.platform,
|
|
3828
4014
|
defaultTerminalShell: resolveTerminalShell(agent.config.getStr('terminal', 'default_shell') || defaultTerminalShell()).id,
|
|
@@ -3880,17 +4066,11 @@ else {
|
|
|
3880
4066
|
const windowInput = targetInput && typeof targetInput === 'object' ? targetInput : {};
|
|
3881
4067
|
const requestedWindow = Math.max(1, Math.floor(Number(windowInput.window) || 0) || 200);
|
|
3882
4068
|
const requestedBefore = Math.max(0, Math.floor(Number(windowInput.before) || 0));
|
|
3883
|
-
const snapshot =
|
|
3884
|
-
? await ensureWslConversationPool().snapshot(target)
|
|
3885
|
-
: await ensureElectronUtilityPool().snapshot(target);
|
|
3886
|
-
const fullMessages = Array.isArray(snapshot.chatMessages) ? snapshot.chatMessages : [];
|
|
3887
|
-
const totalMessages = fullMessages.length;
|
|
3888
|
-
const before = Math.min(totalMessages, requestedBefore);
|
|
3889
|
-
const windowStart = Math.max(0, before - requestedWindow);
|
|
4069
|
+
const snapshot = await runtimeSnapshotForTarget(target, { window: requestedWindow, before: requestedBefore });
|
|
3890
4070
|
return {
|
|
3891
|
-
chatMessages:
|
|
3892
|
-
totalMessages,
|
|
3893
|
-
windowStart,
|
|
4071
|
+
chatMessages: snapshot.chatMessages || [],
|
|
4072
|
+
totalMessages: snapshot.totalMessages || 0,
|
|
4073
|
+
windowStart: snapshot.windowStart || 0,
|
|
3894
4074
|
conversationId: target.conversationId,
|
|
3895
4075
|
workspaceId: target.workspaceId,
|
|
3896
4076
|
};
|
|
@@ -3909,17 +4089,26 @@ else {
|
|
|
3909
4089
|
electron_1.ipcMain.handle('agent:setConversationPinned', async (_event, id, pinned) => {
|
|
3910
4090
|
if (!agent)
|
|
3911
4091
|
return false;
|
|
3912
|
-
|
|
4092
|
+
const ok = agent.setConversationPinned(id, pinned);
|
|
4093
|
+
if (ok)
|
|
4094
|
+
publishConversationList();
|
|
4095
|
+
return ok;
|
|
3913
4096
|
});
|
|
3914
4097
|
electron_1.ipcMain.handle('agent:renameConversation', async (_event, id, title) => {
|
|
3915
4098
|
if (!agent)
|
|
3916
4099
|
return false;
|
|
3917
|
-
|
|
4100
|
+
const ok = agent.renameConversation(id, title);
|
|
4101
|
+
if (ok)
|
|
4102
|
+
publishConversationList();
|
|
4103
|
+
return ok;
|
|
3918
4104
|
});
|
|
3919
4105
|
electron_1.ipcMain.handle('agent:reorderConversations', async (_event, ids) => {
|
|
3920
4106
|
if (!agent)
|
|
3921
4107
|
return false;
|
|
3922
|
-
|
|
4108
|
+
const ok = agent.reorderConversations(ids);
|
|
4109
|
+
if (ok)
|
|
4110
|
+
publishConversationList();
|
|
4111
|
+
return ok;
|
|
3923
4112
|
});
|
|
3924
4113
|
electron_1.ipcMain.handle('automation:list', async () => {
|
|
3925
4114
|
return automation?.list() || [];
|
|
@@ -4087,8 +4276,8 @@ else {
|
|
|
4087
4276
|
electron_1.ipcMain.handle('agent:reloadGlobalConfig', async () => {
|
|
4088
4277
|
if (!agent)
|
|
4089
4278
|
return { error: 'Agent is not initialized' };
|
|
4090
|
-
if (conversationKernel?.
|
|
4091
|
-
return { error: '
|
|
4279
|
+
if (conversationKernel?.hasRetainedWork())
|
|
4280
|
+
return { error: 'Finish or stop retained Flow and queued work before refreshing config.json.' };
|
|
4092
4281
|
try {
|
|
4093
4282
|
agent.config.reload();
|
|
4094
4283
|
if (agent.workspace.current)
|
|
@@ -4103,7 +4292,9 @@ else {
|
|
|
4103
4292
|
throw new AggregateError(failures.map(result => result.reason), 'Runtime cleanup failed');
|
|
4104
4293
|
}), CONFIG_RELOAD_RUNTIME_TIMEOUT_MS, 'Config refresh runtime cleanup');
|
|
4105
4294
|
}
|
|
4295
|
+
conversationKernel?.disposeIdle();
|
|
4106
4296
|
conversationKernel = null;
|
|
4297
|
+
mainConversationOwners.clear();
|
|
4107
4298
|
return { ok: true, path: path.join(agent.rootPath, 'config.json') };
|
|
4108
4299
|
}
|
|
4109
4300
|
catch (error) {
|
|
@@ -4130,6 +4321,15 @@ else {
|
|
|
4130
4321
|
? conversationRuntimeTarget(targetInput)
|
|
4131
4322
|
: conversationRuntimeTarget(agent.activeConversationId || 'default');
|
|
4132
4323
|
const state = activeFlowStateFor(target);
|
|
4324
|
+
const pending = pendingFlowStarts.get(activeFlowStateKey(target));
|
|
4325
|
+
if (pending) {
|
|
4326
|
+
pending.cancelled = true;
|
|
4327
|
+
// Stop can arrive while startup awaits disposal of the previous owner.
|
|
4328
|
+
// Reading a new snapshot before disposal ends re-enters a stopping pool.
|
|
4329
|
+
await pending.settlement;
|
|
4330
|
+
await publishConversationState(target);
|
|
4331
|
+
return { ok: true, action: 'stopping' };
|
|
4332
|
+
}
|
|
4133
4333
|
if (!state)
|
|
4134
4334
|
return { ok: true, action: 'not_running' };
|
|
4135
4335
|
if (state.reason === 'question' || state.reason === 'interrupted') {
|
|
@@ -4196,8 +4396,6 @@ else {
|
|
|
4196
4396
|
if (!agent)
|
|
4197
4397
|
throw new Error('Agent not initialized');
|
|
4198
4398
|
const target = conversationRuntimeTarget({ target: raw?.target });
|
|
4199
|
-
if (activeFlowStateFor(target))
|
|
4200
|
-
clearFlowSuspensionForNewWork(target);
|
|
4201
4399
|
const envelope = {
|
|
4202
4400
|
clientMessageId: String(raw?.clientMessageId || '').trim().slice(0, 200),
|
|
4203
4401
|
target: { workspaceId: target.workspaceId, conversationId: target.conversationId },
|
|
@@ -4208,14 +4406,14 @@ else {
|
|
|
4208
4406
|
images: Array.isArray(raw?.images) ? raw.images : [],
|
|
4209
4407
|
createdAt: String(raw?.createdAt || new Date().toISOString()),
|
|
4210
4408
|
};
|
|
4211
|
-
return
|
|
4212
|
-
? await ensureWslConversationPool().enqueueGuide(envelope)
|
|
4213
|
-
: await ensureElectronUtilityPool().enqueueGuide(envelope);
|
|
4409
|
+
return await enqueueConversationGuide(envelope);
|
|
4214
4410
|
});
|
|
4215
4411
|
electron_1.ipcMain.handle('agent:checkpointConversation', async (_event, request) => {
|
|
4216
4412
|
if (!agent)
|
|
4217
4413
|
throw new Error('Agent not initialized');
|
|
4218
4414
|
const target = conversationRuntimeTarget(request);
|
|
4415
|
+
if (mainConversationOwners.has(activeFlowStateKey(target)))
|
|
4416
|
+
return ensureConversationKernel(root).checkpoint(target);
|
|
4219
4417
|
return wslBackendEnabled()
|
|
4220
4418
|
? await ensureWslConversationPool().checkpoint(target)
|
|
4221
4419
|
: await ensureElectronUtilityPool().checkpoint(target);
|
|
@@ -4228,6 +4426,8 @@ else {
|
|
|
4228
4426
|
keepRecent: Number.isFinite(Number(request?.keepRecent)) ? Math.floor(Number(request.keepRecent)) : undefined,
|
|
4229
4427
|
force: request?.force !== false,
|
|
4230
4428
|
};
|
|
4429
|
+
if (mainConversationOwners.has(activeFlowStateKey(target)))
|
|
4430
|
+
return await ensureConversationKernel(root).compressContext(target, options);
|
|
4231
4431
|
return wslBackendEnabled()
|
|
4232
4432
|
? await ensureWslConversationPool().contextCompress(target, options)
|
|
4233
4433
|
: await ensureElectronUtilityPool().contextCompress(target, options);
|
|
@@ -4238,6 +4438,8 @@ else {
|
|
|
4238
4438
|
const target = conversationRuntimeTarget(request);
|
|
4239
4439
|
const score = Number(request?.score);
|
|
4240
4440
|
const routeId = String(request?.routeId || '');
|
|
4441
|
+
if (mainConversationOwners.has(activeFlowStateKey(target)))
|
|
4442
|
+
return ensureConversationKernel(root).rateAutoRoute(target, score, routeId);
|
|
4241
4443
|
return wslBackendEnabled()
|
|
4242
4444
|
? await ensureWslConversationPool().rateAutoRoute(target, score, routeId)
|
|
4243
4445
|
: await ensureElectronUtilityPool().rateAutoRoute(target, score, routeId);
|
|
@@ -4247,6 +4449,10 @@ else {
|
|
|
4247
4449
|
throw new Error('Agent not initialized');
|
|
4248
4450
|
const target = conversationRuntimeTarget(request);
|
|
4249
4451
|
const runId = String(request?.runId || '').trim() || undefined;
|
|
4452
|
+
if (activeFlowStateFor(target) || pendingFlowStarts.has(activeFlowStateKey(target)))
|
|
4453
|
+
return await stopFlowForTarget(target);
|
|
4454
|
+
if (mainConversationOwners.has(activeFlowStateKey(target)))
|
|
4455
|
+
return ensureConversationKernel(root).requestStop(target, runId);
|
|
4250
4456
|
// The runtime, not a renderer flag, decides whether this is the first
|
|
4251
4457
|
// checkpointing stop or the second target-local hard restart.
|
|
4252
4458
|
return wslBackendEnabled()
|
|
@@ -4260,6 +4466,8 @@ else {
|
|
|
4260
4466
|
const runId = String(request?.runId || '').trim();
|
|
4261
4467
|
if (!runId)
|
|
4262
4468
|
return false;
|
|
4469
|
+
if (mainConversationOwners.has(activeFlowStateKey(target)))
|
|
4470
|
+
return ensureConversationKernel(root).setWorkRunExpanded(target, runId, request?.expanded !== false);
|
|
4263
4471
|
return wslBackendEnabled()
|
|
4264
4472
|
? await ensureWslConversationPool().setWorkRunExpanded(target, runId, request?.expanded !== false)
|
|
4265
4473
|
: await ensureElectronUtilityPool().setWorkRunExpanded(target, runId, request?.expanded !== false);
|
|
@@ -4268,9 +4476,7 @@ else {
|
|
|
4268
4476
|
if (!agent)
|
|
4269
4477
|
return false;
|
|
4270
4478
|
const target = conversationRuntimeTarget(targetInput || agent.activeConversationId || 'default');
|
|
4271
|
-
const result =
|
|
4272
|
-
? await ensureWslConversationPool().requestStop(target)
|
|
4273
|
-
: await ensureElectronUtilityPool().requestStop(target);
|
|
4479
|
+
const result = await applyConversationAction(target, 'conversation_stop');
|
|
4274
4480
|
return result.action !== 'not_running' && result.action !== 'stale';
|
|
4275
4481
|
});
|
|
4276
4482
|
electron_1.ipcMain.handle('agent:rewindConversation', async (_event, targetInput, messageIndex) => {
|
|
@@ -4278,36 +4484,40 @@ else {
|
|
|
4278
4484
|
return { error: 'Agent not initialized' };
|
|
4279
4485
|
const target = conversationRuntimeTarget(targetInput || agent.activeConversationId || 'default');
|
|
4280
4486
|
try {
|
|
4281
|
-
const snapshot = await mutateTargetConversation(target, () =>
|
|
4282
|
-
?
|
|
4283
|
-
: ensureElectronUtilityPool().rewind(target, messageIndex));
|
|
4487
|
+
const snapshot = await mutateTargetConversation(target, () => mainConversationOwners.has(activeFlowStateKey(target))
|
|
4488
|
+
? ensureConversationKernel(root).rewind(target, messageIndex)
|
|
4489
|
+
: wslBackendEnabled() ? ensureWslConversationPool().rewind(target, messageIndex) : ensureElectronUtilityPool().rewind(target, messageIndex));
|
|
4284
4490
|
return { ...snapshot, queued: { steering: [], followUp: [] }, workEvents: [] };
|
|
4285
4491
|
}
|
|
4286
4492
|
catch (error) {
|
|
4287
4493
|
return { error: error instanceof Error ? error.message : String(error) };
|
|
4288
4494
|
}
|
|
4289
4495
|
});
|
|
4290
|
-
|
|
4496
|
+
const branchConversationForTarget = async (targetInput, messageIndex, text, locator) => {
|
|
4291
4497
|
if (!agent)
|
|
4292
4498
|
return { error: 'Agent not initialized' };
|
|
4293
4499
|
const target = conversationRuntimeTarget(targetInput || agent.activeConversationId || 'default');
|
|
4294
4500
|
try {
|
|
4295
4501
|
const snapshot = await mutateTargetConversation(target, () => {
|
|
4296
|
-
const isolated =
|
|
4297
|
-
|
|
4502
|
+
const isolated = conversationAgentForTarget(target);
|
|
4503
|
+
const branched = isolated.branchConversation(target.conversationId, messageIndex, text, locator);
|
|
4504
|
+
if (mainConversationOwners.has(activeFlowStateKey(target)))
|
|
4505
|
+
conversationKernel?.refreshIdleConversation(target);
|
|
4506
|
+
return branched;
|
|
4298
4507
|
});
|
|
4299
4508
|
return { ...snapshot, queued: { steering: [], followUp: [] }, workEvents: [] };
|
|
4300
4509
|
}
|
|
4301
4510
|
catch (error) {
|
|
4302
4511
|
return { error: error instanceof Error ? error.message : String(error) };
|
|
4303
4512
|
}
|
|
4304
|
-
}
|
|
4305
|
-
electron_1.ipcMain.handle('agent:
|
|
4513
|
+
};
|
|
4514
|
+
electron_1.ipcMain.handle('agent:branchConversation', async (_event, targetInput, messageIndex, text, locator) => await branchConversationForTarget(targetInput, messageIndex, text, locator));
|
|
4515
|
+
const inspectConversationBranchForTarget = async (targetInput, branchId, branchGroupId) => {
|
|
4306
4516
|
if (!agent)
|
|
4307
4517
|
return { error: 'Agent not initialized' };
|
|
4308
4518
|
const target = conversationRuntimeTarget(targetInput || agent.activeConversationId || 'default');
|
|
4309
4519
|
try {
|
|
4310
|
-
const isolated =
|
|
4520
|
+
const isolated = conversationAgentForTarget(target);
|
|
4311
4521
|
const snapshot = isolated.inspectConversationBranch(target.conversationId, branchId, branchGroupId);
|
|
4312
4522
|
if (snapshot.runtimeBranchId === snapshot.activeBranchId && peekTargetRuntime(target).resident) {
|
|
4313
4523
|
const live = await runtimeSnapshotForTarget(target);
|
|
@@ -4327,8 +4537,9 @@ else {
|
|
|
4327
4537
|
catch (error) {
|
|
4328
4538
|
return { error: error instanceof Error ? error.message : String(error) };
|
|
4329
4539
|
}
|
|
4330
|
-
}
|
|
4331
|
-
electron_1.ipcMain.handle('agent:
|
|
4540
|
+
};
|
|
4541
|
+
electron_1.ipcMain.handle('agent:inspectConversationBranch', async (_event, targetInput, branchId, branchGroupId) => await inspectConversationBranchForTarget(targetInput, branchId, branchGroupId));
|
|
4542
|
+
const activateConversationBranchForTarget = async (targetInput, branchId, branchGroupId) => {
|
|
4332
4543
|
if (!agent)
|
|
4333
4544
|
return { error: 'Agent not initialized' };
|
|
4334
4545
|
const target = conversationRuntimeTarget(targetInput || agent.activeConversationId || 'default');
|
|
@@ -4336,15 +4547,19 @@ else {
|
|
|
4336
4547
|
if (peekTargetRuntime(target).running)
|
|
4337
4548
|
await stopTargetRuntime(target);
|
|
4338
4549
|
const snapshot = await mutateTargetConversation(target, () => {
|
|
4339
|
-
const isolated =
|
|
4340
|
-
|
|
4550
|
+
const isolated = conversationAgentForTarget(target);
|
|
4551
|
+
const switched = isolated.switchConversationBranch(target.conversationId, branchId, branchGroupId);
|
|
4552
|
+
if (mainConversationOwners.has(activeFlowStateKey(target)))
|
|
4553
|
+
conversationKernel?.refreshIdleConversation(target);
|
|
4554
|
+
return switched;
|
|
4341
4555
|
});
|
|
4342
4556
|
return { ...snapshot, queued: { steering: [], followUp: [] }, workEvents: [] };
|
|
4343
4557
|
}
|
|
4344
4558
|
catch (error) {
|
|
4345
4559
|
return { error: error instanceof Error ? error.message : String(error) };
|
|
4346
4560
|
}
|
|
4347
|
-
}
|
|
4561
|
+
};
|
|
4562
|
+
electron_1.ipcMain.handle('agent:activateConversationBranch', async (_event, targetInput, branchId, branchGroupId) => await activateConversationBranchForTarget(targetInput, branchId, branchGroupId));
|
|
4348
4563
|
electron_1.ipcMain.handle('flow:list', async () => {
|
|
4349
4564
|
if (!agent)
|
|
4350
4565
|
return [];
|
|
@@ -4376,7 +4591,7 @@ else {
|
|
|
4376
4591
|
flow_1.FlowEngine.save(flowDir, workflow);
|
|
4377
4592
|
return { ok: true, workflow };
|
|
4378
4593
|
});
|
|
4379
|
-
|
|
4594
|
+
const archiveConversationForTarget = async (targetInput) => {
|
|
4380
4595
|
if (!agent)
|
|
4381
4596
|
return { ok: false, error: 'Agent not initialized' };
|
|
4382
4597
|
const target = conversationRuntimeTarget(targetInput || agent.activeConversationId || 'default');
|
|
@@ -4386,13 +4601,20 @@ else {
|
|
|
4386
4601
|
return existing;
|
|
4387
4602
|
const operation = (async () => {
|
|
4388
4603
|
mutatingRuntimeKeys.add(normalized.runtimeKey);
|
|
4604
|
+
let completed = false;
|
|
4389
4605
|
try {
|
|
4390
4606
|
// Archive is a destructive lifecycle command. It intentionally
|
|
4391
4607
|
// bypasses the normal mutation/active-prompt guard. Cancel the
|
|
4392
4608
|
// conversation-local Flow synchronously, then start the resident
|
|
4393
4609
|
// runtime hard-stop in the background so a stuck child cannot hold
|
|
4394
4610
|
// the archive click hostage.
|
|
4611
|
+
const ownedFlow = activeFlowStateFor(normalized);
|
|
4612
|
+
const mainOwnerPreparation = mainConversationOwners.has(normalized.runtimeKey)
|
|
4613
|
+
? ensureConversationKernel(root).prepareForArchive(normalized) : Promise.resolve(null);
|
|
4395
4614
|
interruptActiveFlowForArchive(normalized);
|
|
4615
|
+
const mainArchiveOwner = await mainOwnerPreparation;
|
|
4616
|
+
if (ownedFlow?.settlement)
|
|
4617
|
+
await ownedFlow.settlement;
|
|
4396
4618
|
void forceStopTargetRuntime(normalized).catch(error => {
|
|
4397
4619
|
console.error('[Newmark] archive runtime force-stop failed:', error instanceof Error ? error.message : String(error));
|
|
4398
4620
|
});
|
|
@@ -4406,17 +4628,23 @@ else {
|
|
|
4406
4628
|
// starts payload I/O in parallel and finalizes deletion against the
|
|
4407
4629
|
// latest locked state snapshot, so rapid clicks do not serialize on
|
|
4408
4630
|
// large Markdown bodies or lose a sibling deletion.
|
|
4409
|
-
const archiveOwner = ownsTargetWorkspace ? agent : isolatedConversationAgent(normalized);
|
|
4631
|
+
const archiveOwner = mainArchiveOwner || (ownsTargetWorkspace ? agent : isolatedConversationAgent(normalized));
|
|
4410
4632
|
archiveOwner.clearStoredFlowSuspension(normalized.conversationId);
|
|
4411
4633
|
const archived = await archiveOwner.archiveConversationAsync(normalized.conversationId);
|
|
4412
4634
|
if (!archived)
|
|
4413
4635
|
return { ok: false, error: 'Conversation archive could not be written.' };
|
|
4636
|
+
completed = true;
|
|
4637
|
+
conversationSelections.delete(normalized.runtimeKey);
|
|
4638
|
+
publishConversationList((normalized.workspace || agent.workspace.current));
|
|
4414
4639
|
return { ok: true, fileName: archived, conversationId: normalized.conversationId, workspaceId: normalized.workspaceId };
|
|
4415
4640
|
}
|
|
4416
4641
|
catch (error) {
|
|
4417
4642
|
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
4418
4643
|
}
|
|
4419
4644
|
finally {
|
|
4645
|
+
conversationKernel?.finishArchive(normalized, completed);
|
|
4646
|
+
if (completed)
|
|
4647
|
+
mainConversationOwners.delete(normalized.runtimeKey);
|
|
4420
4648
|
mutatingRuntimeKeys.delete(normalized.runtimeKey);
|
|
4421
4649
|
}
|
|
4422
4650
|
})();
|
|
@@ -4428,7 +4656,8 @@ else {
|
|
|
4428
4656
|
if (archiveInFlight.get(normalized.runtimeKey) === operation)
|
|
4429
4657
|
archiveInFlight.delete(normalized.runtimeKey);
|
|
4430
4658
|
}
|
|
4431
|
-
}
|
|
4659
|
+
};
|
|
4660
|
+
electron_1.ipcMain.handle('agent:archive', async (_event, targetInput) => await archiveConversationForTarget(targetInput));
|
|
4432
4661
|
electron_1.ipcMain.handle('agent:listArchives', async (_event, scope) => {
|
|
4433
4662
|
if (!agent)
|
|
4434
4663
|
return [];
|
|
@@ -4452,6 +4681,7 @@ else {
|
|
|
4452
4681
|
return restored;
|
|
4453
4682
|
agent.selectWorkspaceFromStorage(restored.workspaceId);
|
|
4454
4683
|
agent.setConversationFromStorage(restored.conversationId);
|
|
4684
|
+
publishConversationList();
|
|
4455
4685
|
return { ...restored, snapshot: agent.getConversationSnapshot(restored.conversationId) };
|
|
4456
4686
|
});
|
|
4457
4687
|
async function listTreeLevel(current) {
|
|
@@ -4600,16 +4830,9 @@ else {
|
|
|
4600
4830
|
if (mainWindow && !mainWindow.isDestroyed())
|
|
4601
4831
|
mainWindow.webContents.send('pty:data', sessionId, text);
|
|
4602
4832
|
});
|
|
4603
|
-
electron_1.ipcMain.handle('agent:queueAction', async (_event,
|
|
4604
|
-
const target = conversationRuntimeTarget(targetInput);
|
|
4605
|
-
const action = String(actionInput || '').replace(/^queue_/, '');
|
|
4606
|
-
if (!['enqueue', 'update', 'delete', 'reorder', 'toggle_pause', 'guide'].includes(action)) {
|
|
4607
|
-
return { ok: false, error: 'Unknown queue action' };
|
|
4608
|
-
}
|
|
4833
|
+
electron_1.ipcMain.handle('agent:queueAction', async (_event, action, input = {}, target) => {
|
|
4609
4834
|
try {
|
|
4610
|
-
return
|
|
4611
|
-
? await ensureWslConversationPool().queueAction(target, action, input)
|
|
4612
|
-
: await ensureElectronUtilityPool().queueAction(target, action, input);
|
|
4835
|
+
return await applyConversationAction(conversationRuntimeTarget(target), 'queue_' + action.replace(/^queue_/, ''), '', input);
|
|
4613
4836
|
}
|
|
4614
4837
|
catch (error) {
|
|
4615
4838
|
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
@@ -4911,6 +5134,9 @@ else {
|
|
|
4911
5134
|
]);
|
|
4912
5135
|
if (nativeActive || wslActive)
|
|
4913
5136
|
throw new Error('Cannot delete a workspace while one of its conversations is running, stopping, or restarting.');
|
|
5137
|
+
const releasedOwners = conversationKernel?.disposeIdle(normalized.workspaceKey) || [];
|
|
5138
|
+
for (const key of releasedOwners)
|
|
5139
|
+
mainConversationOwners.delete(key);
|
|
4914
5140
|
await Promise.all([
|
|
4915
5141
|
electronUtilityRuntimePool?.stopWorkspace(normalized),
|
|
4916
5142
|
wslAgentRuntimePool?.stopWorkspace(normalized),
|