mixdog 0.9.67 → 0.9.68
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/package.json +1 -1
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +10 -2
- package/src/runtime/channels/lib/config.mjs +6 -5
- package/src/runtime/channels/lib/owned-runtime.mjs +65 -5
- package/src/runtime/channels/lib/scheduler.mjs +7 -15
- package/src/runtime/channels/lib/tool-dispatch.mjs +6 -1
- package/src/runtime/channels/lib/webhook.mjs +23 -45
- package/src/runtime/channels/lib/worker-main.mjs +45 -92
- package/src/runtime/shared/automation-attachments.mjs +72 -0
- package/src/runtime/shared/automation-workflow.mjs +41 -0
- package/src/runtime/shared/schedule-session-run.mjs +10 -1
- package/src/runtime/shared/schedules-db.mjs +18 -3
- package/src/runtime/shared/webhook-session-run.mjs +57 -0
- package/src/runtime/shared/webhooks-db.mjs +19 -3
- package/src/session-runtime/channel-config-api.mjs +8 -1
- package/src/session-runtime/lifecycle-api.mjs +7 -0
- package/src/session-runtime/runtime-core.mjs +103 -17
- package/src/standalone/channel-admin.mjs +36 -1
- package/src/standalone/channel-daemon-client.mjs +5 -1
- package/src/standalone/channel-daemon-transport.mjs +112 -20
- package/src/standalone/channel-daemon.mjs +6 -4
- package/src/tui/App.jsx +2 -32
- package/src/tui/app/channel-pickers.mjs +2 -143
- package/src/tui/app/slash-commands.mjs +1 -3
- package/src/tui/app/slash-dispatch.mjs +3 -3
- package/src/tui/dist/index.mjs +42 -169
- package/src/tui/engine/session-api-ext.mjs +32 -6
- package/src/tui/engine.mjs +14 -1
package/src/tui/dist/index.mjs
CHANGED
|
@@ -10093,10 +10093,8 @@ var SLASH_COMMANDS = [
|
|
|
10093
10093
|
{ name: "plugins", usage: "/plugins", description: "Manage local plugin integrations" },
|
|
10094
10094
|
{ name: "hooks", usage: "/hooks", description: "Manage before-tool hook rules and events" },
|
|
10095
10095
|
{ name: "providers", usage: "/providers", description: "Manage auth, API keys, OAuth, and local endpoints" },
|
|
10096
|
-
{ name: "channels", usage: "/channels", description: "Manage Discord,
|
|
10096
|
+
{ name: "channels", usage: "/channels", description: "Manage Discord, Telegram, and voice" },
|
|
10097
10097
|
{ name: "remote", usage: "/remote", description: "Claim remote for this session (takes over from any other session)" },
|
|
10098
|
-
{ name: "schedules", usage: "/schedules", description: "Manage schedules" },
|
|
10099
|
-
{ name: "webhooks", usage: "/webhooks", description: "Manage inbound webhooks" },
|
|
10100
10098
|
{ name: "settings", usage: "/setting", aliases: ["setting", "config"], aliasUsage: ["settings", "config"], showAliasUsage: false, description: "Open runtime settings" },
|
|
10101
10099
|
{ name: "profile", usage: "/profile", description: "Set your title and response language" },
|
|
10102
10100
|
{ name: "update", usage: "/update", description: "Check version and update mixdog" },
|
|
@@ -17344,139 +17342,6 @@ function createChannelPickers({
|
|
|
17344
17342
|
setContextPanel(null);
|
|
17345
17343
|
setChannelPrompt(prompt);
|
|
17346
17344
|
};
|
|
17347
|
-
const channelRemoteEnabled = store.isRemoteEnabled?.() === true;
|
|
17348
|
-
if (focus === "schedules") {
|
|
17349
|
-
const schedules = setup.schedules || [];
|
|
17350
|
-
const items2 = [
|
|
17351
|
-
...schedules.length ? schedules.map((schedule) => {
|
|
17352
|
-
const enabled = schedule.enabled !== false;
|
|
17353
|
-
return {
|
|
17354
|
-
value: `schedule:${schedule.name}`,
|
|
17355
|
-
label: schedule.name,
|
|
17356
|
-
marker: enabled ? "\u25CF" : "\u25CB",
|
|
17357
|
-
markerColor: channelRemoteEnabled ? enabled ? theme.success : theme.inactive : theme.inactive,
|
|
17358
|
-
description: `${schedule.time || "(no cron)"} \xB7 ${schedule.route}${schedule.model ? ` \xB7 ${schedule.model}` : ""}${channelRemoteEnabled ? "" : " \xB7 channel off"}`,
|
|
17359
|
-
_action: "schedule-toggle",
|
|
17360
|
-
_name: schedule.name,
|
|
17361
|
-
_enabled: enabled
|
|
17362
|
-
};
|
|
17363
|
-
}) : [{
|
|
17364
|
-
value: "empty",
|
|
17365
|
-
label: "No schedules",
|
|
17366
|
-
description: "no schedules configured",
|
|
17367
|
-
_action: "noop"
|
|
17368
|
-
}]
|
|
17369
|
-
];
|
|
17370
|
-
const toggleSchedule = async (item) => {
|
|
17371
|
-
if (item._action !== "schedule-toggle") return;
|
|
17372
|
-
if (!channelRemoteEnabled) {
|
|
17373
|
-
store.pushNotice("enable channel first", "warn");
|
|
17374
|
-
return;
|
|
17375
|
-
}
|
|
17376
|
-
try {
|
|
17377
|
-
await store.setScheduleEnabled?.(item._name, !item._enabled);
|
|
17378
|
-
void openChannelSetupPicker("schedules", { highlightValue: `schedule:${item._name}` });
|
|
17379
|
-
} catch (e) {
|
|
17380
|
-
store.pushNotice(`schedule toggle failed: ${e?.message || e}`, "error");
|
|
17381
|
-
}
|
|
17382
|
-
};
|
|
17383
|
-
setPicker({
|
|
17384
|
-
title: "Schedules",
|
|
17385
|
-
description: channelRemoteEnabled ? "Enable or disable cron schedules." : "Enable channel to toggle schedules.",
|
|
17386
|
-
initialIndex: Math.max(0, items2.findIndex((entry) => entry.value === options.highlightValue)),
|
|
17387
|
-
items: items2,
|
|
17388
|
-
onSelect: (_value, item) => toggleSchedule(item),
|
|
17389
|
-
onLeft: (item) => toggleSchedule(item),
|
|
17390
|
-
onRight: (item) => toggleSchedule(item),
|
|
17391
|
-
onCancel: () => {
|
|
17392
|
-
setPicker(null);
|
|
17393
|
-
}
|
|
17394
|
-
});
|
|
17395
|
-
return;
|
|
17396
|
-
}
|
|
17397
|
-
if (focus === "webhook-endpoint") {
|
|
17398
|
-
const returnTo = typeof options.returnTo === "function" ? options.returnTo : () => setPicker(null);
|
|
17399
|
-
const publicUrl = setup.webhook?.publicUrl || "";
|
|
17400
|
-
const items2 = [
|
|
17401
|
-
{
|
|
17402
|
-
value: "endpoint-url",
|
|
17403
|
-
label: "Public URL",
|
|
17404
|
-
description: publicUrl ? `${publicUrl}/webhook/<name>` : "Assigned when the channel worker connects to the relay",
|
|
17405
|
-
_action: "endpoint-url"
|
|
17406
|
-
}
|
|
17407
|
-
];
|
|
17408
|
-
setPicker({
|
|
17409
|
-
title: "Webhook endpoint",
|
|
17410
|
-
description: "Served through the Mixdog relay \u2014 no tunnel setup needed. Toggle individual webhooks in /webhooks.",
|
|
17411
|
-
help: "\u2191/\u2193 Select \xB7 Enter Edit \xB7 Esc Back",
|
|
17412
|
-
indexMode: "always",
|
|
17413
|
-
labelWidth: 18,
|
|
17414
|
-
items: items2,
|
|
17415
|
-
onSelect: (_value, item) => {
|
|
17416
|
-
try {
|
|
17417
|
-
if (item._action === "endpoint-url") {
|
|
17418
|
-
store.pushNotice(publicUrl ? `Webhook base: ${publicUrl}/webhook/<name>` : "URL not assigned yet \u2014 start channels once and reopen.", "info");
|
|
17419
|
-
}
|
|
17420
|
-
} catch (e) {
|
|
17421
|
-
store.pushNotice(`webhook endpoint failed: ${e?.message || e}`, "error");
|
|
17422
|
-
}
|
|
17423
|
-
},
|
|
17424
|
-
onCancel: () => {
|
|
17425
|
-
setPicker(null);
|
|
17426
|
-
returnTo();
|
|
17427
|
-
}
|
|
17428
|
-
});
|
|
17429
|
-
return;
|
|
17430
|
-
}
|
|
17431
|
-
if (focus === "webhooks") {
|
|
17432
|
-
const hooks = setup.webhooks || [];
|
|
17433
|
-
const items2 = [
|
|
17434
|
-
...hooks.length ? hooks.map((hook) => {
|
|
17435
|
-
const enabled = hook.enabled !== false;
|
|
17436
|
-
return {
|
|
17437
|
-
value: `webhook:${hook.name}`,
|
|
17438
|
-
label: hook.name,
|
|
17439
|
-
marker: enabled ? "\u25CF" : "\u25CB",
|
|
17440
|
-
markerColor: channelRemoteEnabled ? enabled ? theme.success : theme.inactive : theme.inactive,
|
|
17441
|
-
description: `${hook.parser || "github"} \xB7 ${hook.route} \xB7 secret:${hook.secretSet ? "set" : "missing"}${channelRemoteEnabled ? "" : " \xB7 channel off"}`,
|
|
17442
|
-
_action: "webhook-toggle",
|
|
17443
|
-
_name: hook.name,
|
|
17444
|
-
_enabled: enabled
|
|
17445
|
-
};
|
|
17446
|
-
}) : [{
|
|
17447
|
-
value: "empty",
|
|
17448
|
-
label: "No webhooks",
|
|
17449
|
-
description: "no webhook endpoints configured",
|
|
17450
|
-
_action: "noop"
|
|
17451
|
-
}]
|
|
17452
|
-
];
|
|
17453
|
-
const toggleWebhook = async (item) => {
|
|
17454
|
-
if (item._action !== "webhook-toggle") return;
|
|
17455
|
-
if (!channelRemoteEnabled) {
|
|
17456
|
-
store.pushNotice("enable channel first", "warn");
|
|
17457
|
-
return;
|
|
17458
|
-
}
|
|
17459
|
-
try {
|
|
17460
|
-
await store.setWebhookEnabled?.(item._name, !item._enabled);
|
|
17461
|
-
void openChannelSetupPicker("webhooks", { highlightValue: `webhook:${item._name}` });
|
|
17462
|
-
} catch (e) {
|
|
17463
|
-
store.pushNotice(`webhook toggle failed: ${e?.message || e}`, "error");
|
|
17464
|
-
}
|
|
17465
|
-
};
|
|
17466
|
-
setPicker({
|
|
17467
|
-
title: "Webhooks",
|
|
17468
|
-
description: channelRemoteEnabled ? "Enable or disable inbound webhook endpoints." : "Enable channel to toggle webhooks.",
|
|
17469
|
-
initialIndex: Math.max(0, items2.findIndex((entry) => entry.value === options.highlightValue)),
|
|
17470
|
-
items: items2,
|
|
17471
|
-
onSelect: (_value, item) => toggleWebhook(item),
|
|
17472
|
-
onLeft: (item) => toggleWebhook(item),
|
|
17473
|
-
onRight: (item) => toggleWebhook(item),
|
|
17474
|
-
onCancel: () => {
|
|
17475
|
-
setPicker(null);
|
|
17476
|
-
}
|
|
17477
|
-
});
|
|
17478
|
-
return;
|
|
17479
|
-
}
|
|
17480
17345
|
const worker = store.getChannelWorkerStatus?.();
|
|
17481
17346
|
const activeBackend = setup.backend === "telegram" ? "telegram" : "discord";
|
|
17482
17347
|
const backendLabel = activeBackend === "telegram" ? "Telegram" : "Discord";
|
|
@@ -19574,10 +19439,8 @@ function createSlashDispatch({
|
|
|
19574
19439
|
void openChannelSetupPicker("all");
|
|
19575
19440
|
return true;
|
|
19576
19441
|
case "schedules":
|
|
19577
|
-
void openChannelSetupPicker("schedules");
|
|
19578
|
-
return true;
|
|
19579
19442
|
case "webhooks":
|
|
19580
|
-
|
|
19443
|
+
store.pushNotice("Schedules and webhooks are managed in the Mixdog desktop app", "info");
|
|
19581
19444
|
return true;
|
|
19582
19445
|
case "auth":
|
|
19583
19446
|
store.pushNotice("/auth moved to /providers", "info");
|
|
@@ -22830,29 +22693,6 @@ function App({ store, initialStatusLine = "", forceOnboarding = false }) {
|
|
|
22830
22693
|
});
|
|
22831
22694
|
return true;
|
|
22832
22695
|
}
|
|
22833
|
-
if (channelPrompt.kind === "schedule-add") {
|
|
22834
|
-
const [name, time, instructions, channel, model] = parts;
|
|
22835
|
-
Promise.resolve(store.saveSchedule({ name, time, instructions, channel, model })).then(() => {
|
|
22836
|
-
setChannelPrompt(null);
|
|
22837
|
-
void openChannelSetupPicker("schedules");
|
|
22838
|
-
}).catch((e) => {
|
|
22839
|
-
store.pushNotice(`schedule save failed: ${e?.message || e}`, "error");
|
|
22840
|
-
});
|
|
22841
|
-
return true;
|
|
22842
|
-
}
|
|
22843
|
-
if (channelPrompt.kind === "webhook-add") {
|
|
22844
|
-
const [name, instructions, channel, model, parser] = parts;
|
|
22845
|
-
Promise.resolve(store.saveWebhook({ name, instructions, channel, model, parser })).then((result) => {
|
|
22846
|
-
if (result?.secret) {
|
|
22847
|
-
store.pushNotice(`webhook secret for ${result.name}: ${result.secret}`, "info");
|
|
22848
|
-
}
|
|
22849
|
-
setChannelPrompt(null);
|
|
22850
|
-
void openChannelSetupPicker("webhooks");
|
|
22851
|
-
}).catch((e) => {
|
|
22852
|
-
store.pushNotice(`webhook save failed: ${e?.message || e}`, "error");
|
|
22853
|
-
});
|
|
22854
|
-
return true;
|
|
22855
|
-
}
|
|
22856
22696
|
} catch (e) {
|
|
22857
22697
|
store.pushNotice(`channels update failed: ${e?.message || e}`, "error");
|
|
22858
22698
|
return false;
|
|
@@ -28278,15 +28118,30 @@ function createEngineApiB(bag) {
|
|
|
28278
28118
|
set({ commandBusy: false });
|
|
28279
28119
|
}
|
|
28280
28120
|
},
|
|
28281
|
-
// Toggle
|
|
28282
|
-
//
|
|
28283
|
-
//
|
|
28121
|
+
// Toggle channel remote mode for the CURRENT session (session-scoped,
|
|
28122
|
+
// user decision). Off → on claims for this session; on + this session
|
|
28123
|
+
// owns → off; on + ANOTHER session owns → move the relay seat here
|
|
28124
|
+
// (last-wins) without restarting the worker.
|
|
28284
28125
|
toggleRemote: () => {
|
|
28285
28126
|
const enabled = runtime.isRemoteEnabled?.() === true;
|
|
28286
|
-
|
|
28127
|
+
const owner = String(runtime.getRemoteSessionId?.() || "");
|
|
28128
|
+
const currentId = String(getState().sessionId || "");
|
|
28129
|
+
const workerRunning = runtime.getChannelWorkerStatus?.()?.running === true;
|
|
28130
|
+
if (enabled && !workerRunning) {
|
|
28131
|
+
runtime.startRemote?.();
|
|
28132
|
+
const next2 = runtime.isRemoteEnabled?.() === true;
|
|
28133
|
+
set({ remoteEnabled: next2, remoteSessionId: runtime.getRemoteSessionId?.() || null });
|
|
28134
|
+
return next2;
|
|
28135
|
+
}
|
|
28136
|
+
if (enabled && owner && currentId && owner !== currentId) {
|
|
28137
|
+
runtime.claimRemoteForCurrentSession?.();
|
|
28138
|
+
set({ remoteEnabled: true, remoteSessionId: runtime.getRemoteSessionId?.() || null });
|
|
28139
|
+
return true;
|
|
28140
|
+
}
|
|
28141
|
+
if (enabled) runtime.releaseRemote?.();
|
|
28287
28142
|
else runtime.startRemote?.();
|
|
28288
28143
|
const next = runtime.isRemoteEnabled?.() === true;
|
|
28289
|
-
set({ remoteEnabled: next });
|
|
28144
|
+
set({ remoteEnabled: next, remoteSessionId: runtime.getRemoteSessionId?.() || null });
|
|
28290
28145
|
return next;
|
|
28291
28146
|
},
|
|
28292
28147
|
// Force-claim remote for this session (single-holder, last-wins). Always
|
|
@@ -28296,9 +28151,16 @@ function createEngineApiB(bag) {
|
|
|
28296
28151
|
claimRemote: () => {
|
|
28297
28152
|
runtime.startRemote?.();
|
|
28298
28153
|
const next = runtime.isRemoteEnabled?.() === true;
|
|
28299
|
-
set({ remoteEnabled: next });
|
|
28154
|
+
set({ remoteEnabled: next, remoteSessionId: runtime.getRemoteSessionId?.() || null });
|
|
28300
28155
|
return next;
|
|
28301
28156
|
},
|
|
28157
|
+
// Idempotent desired-state OFF for desktop controls. Unlike toggleRemote,
|
|
28158
|
+
// a delayed/replayed request can never turn remote back on.
|
|
28159
|
+
releaseRemote: () => {
|
|
28160
|
+
runtime.releaseRemote?.();
|
|
28161
|
+
set({ remoteEnabled: false, remoteSessionId: null });
|
|
28162
|
+
return false;
|
|
28163
|
+
},
|
|
28302
28164
|
isRemoteEnabled: () => runtime.isRemoteEnabled?.() === true,
|
|
28303
28165
|
getVoiceStatus: () => getVoiceStatus(),
|
|
28304
28166
|
// Desktop push-to-talk dictation: accept a recorded audio payload
|
|
@@ -31163,12 +31025,23 @@ async function createEngineSession({
|
|
|
31163
31025
|
if (typeof runtime.onRemoteStateChange === "function") {
|
|
31164
31026
|
lifecycle.unsubscribeRemoteState = runtime.onRemoteStateChange(({ enabled, reason }) => {
|
|
31165
31027
|
if (flags.disposed) return;
|
|
31166
|
-
set({
|
|
31028
|
+
set({
|
|
31029
|
+
remoteEnabled: enabled === true,
|
|
31030
|
+
remoteSessionId: runtime.getRemoteSessionId?.() || null
|
|
31031
|
+
});
|
|
31167
31032
|
if (reason === "superseded") {
|
|
31168
31033
|
pushNotice("Remote mode OFF \u2014 another session took over remote.", "warn");
|
|
31034
|
+
} else if (reason === "claim-failed") {
|
|
31035
|
+
pushNotice("Remote mode could not start. Check the channel connection and try again.", "error");
|
|
31036
|
+
} else if (reason === "release-failed") {
|
|
31037
|
+
pushNotice("Remote mode could not be stopped cleanly. The shared state will reconcile automatically.", "error");
|
|
31169
31038
|
}
|
|
31170
31039
|
});
|
|
31171
31040
|
}
|
|
31041
|
+
set({
|
|
31042
|
+
remoteEnabled: runtime.isRemoteEnabled?.() === true,
|
|
31043
|
+
remoteSessionId: runtime.getRemoteSessionId?.() || null
|
|
31044
|
+
});
|
|
31172
31045
|
const { patchToolCardResult, flushToolResults } = createToolCardResults({
|
|
31173
31046
|
getState: () => state,
|
|
31174
31047
|
set,
|
|
@@ -403,15 +403,34 @@ export function createEngineApiB(bag) {
|
|
|
403
403
|
set({ commandBusy: false });
|
|
404
404
|
}
|
|
405
405
|
},
|
|
406
|
-
// Toggle
|
|
407
|
-
//
|
|
408
|
-
//
|
|
406
|
+
// Toggle channel remote mode for the CURRENT session (session-scoped,
|
|
407
|
+
// user decision). Off → on claims for this session; on + this session
|
|
408
|
+
// owns → off; on + ANOTHER session owns → move the relay seat here
|
|
409
|
+
// (last-wins) without restarting the worker.
|
|
409
410
|
toggleRemote: () => {
|
|
410
411
|
const enabled = runtime.isRemoteEnabled?.() === true;
|
|
411
|
-
|
|
412
|
+
const owner = String(runtime.getRemoteSessionId?.() || '');
|
|
413
|
+
const currentId = String(getState().sessionId || '');
|
|
414
|
+
// Stale-enabled guard: boot auto-acquire can leave remote reading ON
|
|
415
|
+
// while the channels worker already self-shut (no live clients). A
|
|
416
|
+
// toggle in that state must BOOT the worker for this session, not
|
|
417
|
+
// no-op into a claim or flip a dead relay "off".
|
|
418
|
+
const workerRunning = runtime.getChannelWorkerStatus?.()?.running === true;
|
|
419
|
+
if (enabled && !workerRunning) {
|
|
420
|
+
runtime.startRemote?.();
|
|
421
|
+
const next = runtime.isRemoteEnabled?.() === true;
|
|
422
|
+
set({ remoteEnabled: next, remoteSessionId: runtime.getRemoteSessionId?.() || null });
|
|
423
|
+
return next;
|
|
424
|
+
}
|
|
425
|
+
if (enabled && owner && currentId && owner !== currentId) {
|
|
426
|
+
runtime.claimRemoteForCurrentSession?.();
|
|
427
|
+
set({ remoteEnabled: true, remoteSessionId: runtime.getRemoteSessionId?.() || null });
|
|
428
|
+
return true;
|
|
429
|
+
}
|
|
430
|
+
if (enabled) runtime.releaseRemote?.();
|
|
412
431
|
else runtime.startRemote?.();
|
|
413
432
|
const next = runtime.isRemoteEnabled?.() === true;
|
|
414
|
-
set({ remoteEnabled: next });
|
|
433
|
+
set({ remoteEnabled: next, remoteSessionId: runtime.getRemoteSessionId?.() || null });
|
|
415
434
|
return next;
|
|
416
435
|
},
|
|
417
436
|
// Force-claim remote for this session (single-holder, last-wins). Always
|
|
@@ -421,9 +440,16 @@ export function createEngineApiB(bag) {
|
|
|
421
440
|
claimRemote: () => {
|
|
422
441
|
runtime.startRemote?.();
|
|
423
442
|
const next = runtime.isRemoteEnabled?.() === true;
|
|
424
|
-
set({ remoteEnabled: next });
|
|
443
|
+
set({ remoteEnabled: next, remoteSessionId: runtime.getRemoteSessionId?.() || null });
|
|
425
444
|
return next;
|
|
426
445
|
},
|
|
446
|
+
// Idempotent desired-state OFF for desktop controls. Unlike toggleRemote,
|
|
447
|
+
// a delayed/replayed request can never turn remote back on.
|
|
448
|
+
releaseRemote: () => {
|
|
449
|
+
runtime.releaseRemote?.();
|
|
450
|
+
set({ remoteEnabled: false, remoteSessionId: null });
|
|
451
|
+
return false;
|
|
452
|
+
},
|
|
427
453
|
isRemoteEnabled: () => runtime.isRemoteEnabled?.() === true,
|
|
428
454
|
getVoiceStatus: () => getVoiceStatus(),
|
|
429
455
|
// Desktop push-to-talk dictation: accept a recorded audio payload
|
package/src/tui/engine.mjs
CHANGED
|
@@ -761,12 +761,25 @@ export async function createEngineSession({
|
|
|
761
761
|
if (typeof runtime.onRemoteStateChange === 'function') {
|
|
762
762
|
lifecycle.unsubscribeRemoteState = runtime.onRemoteStateChange(({ enabled, reason }) => {
|
|
763
763
|
if (flags.disposed) return;
|
|
764
|
-
set({
|
|
764
|
+
set({
|
|
765
|
+
remoteEnabled: enabled === true,
|
|
766
|
+
remoteSessionId: runtime.getRemoteSessionId?.() || null,
|
|
767
|
+
});
|
|
765
768
|
if (reason === 'superseded') {
|
|
766
769
|
pushNotice('Remote mode OFF — another session took over remote.', 'warn');
|
|
770
|
+
} else if (reason === 'claim-failed') {
|
|
771
|
+
pushNotice('Remote mode could not start. Check the channel connection and try again.', 'error');
|
|
772
|
+
} else if (reason === 'release-failed') {
|
|
773
|
+
pushNotice('Remote mode could not be stopped cleanly. The shared state will reconcile automatically.', 'error');
|
|
767
774
|
}
|
|
768
775
|
});
|
|
769
776
|
}
|
|
777
|
+
// Seed the session-scoped remote fields so the desktop header button shows
|
|
778
|
+
// the real state before the first toggle/notification.
|
|
779
|
+
set({
|
|
780
|
+
remoteEnabled: runtime.isRemoteEnabled?.() === true,
|
|
781
|
+
remoteSessionId: runtime.getRemoteSessionId?.() || null,
|
|
782
|
+
});
|
|
770
783
|
|
|
771
784
|
const { patchToolCardResult, flushToolResults } = createToolCardResults({
|
|
772
785
|
getState: () => state,
|