@aliceshimada/mica 1.0.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.
@@ -0,0 +1,1831 @@
1
+ BeginPackage["MMAAgentBridge`"];
2
+
3
+ StartMMAAgentPalette::usage = "StartMMAAgentPalette[] opens the MMA Agent Bridge palette.";
4
+ StartMMAAgentHiddenAgent::usage = "StartMMAAgentHiddenAgent[] starts the hidden Wolfram agent loop.";
5
+ StartMMAAgentControlKernel::usage = "StartMMAAgentControlKernel[] starts the hidden Wolfram agent in a dedicated FrontEnd evaluator.";
6
+ StopMMAAgentHiddenAgent::usage = "StopMMAAgentHiddenAgent[] stops the hidden Wolfram agent loop in the current kernel.";
7
+ StopMMAAgentControlKernel::usage = "StopMMAAgentControlKernel[] closes the hidden control-kernel notebook if this kernel created it.";
8
+ AttachCurrentNotebook::usage = "AttachCurrentNotebook[] attaches the current input notebook to the local MCP bridge.";
9
+ PollBridge::usage = "PollBridge[] polls the local bridge for one pending request and executes it.";
10
+ ExecuteRequest::usage = "ExecuteRequest[assoc] executes one bridge request.";
11
+ PostResult::usage = "PostResult[requestId, result] posts a successful result to the bridge.";
12
+ PostFailure::usage = "PostFailure[requestId, code, message] posts a failure result to the bridge.";
13
+ CancelCurrentRequest::usage = "CancelCurrentRequest[] cancels the current request and attempts to abort evaluation.";
14
+ PollCancellations::usage = "PollCancellations[] polls palette-originated cancellation notices.";
15
+
16
+ Begin["`Private`"];
17
+
18
+ $DefaultBridgeBaseURL = "http://127.0.0.1:19791";
19
+ $BridgeBaseURL = $DefaultBridgeBaseURL;
20
+ $BridgeAuthToken = None;
21
+ $BridgeSessionFile = Automatic;
22
+ $BridgeNotebooks = <||>;
23
+ $ActiveNotebookId = None;
24
+ $PaletteId = CreateUUID["palette-"];
25
+ $AttachedNotebook = None;
26
+ $AttachedNotebookInfo = <||>;
27
+ $CellMap = <||>;
28
+ $NextCellId = 1;
29
+ $CurrentRequestId = None;
30
+ $RunningRequestId = None;
31
+ $RunningCellId = None;
32
+ $RunningNotebookId = None;
33
+ $RunningNotebookObject = None;
34
+ $RunningCellObject = None;
35
+ $RunningCellOriginalEpilogRule = HoldComplete[CellEpilog -> Inherited];
36
+ $RunningCellRestoreEpilogRule = HoldComplete[CellEpilog -> Inherited];
37
+ $RunningStartedAt = None;
38
+ $RunningStatus = None;
39
+ $RunningStatusGraceSeconds = 2.0;
40
+ $RunningTimeoutAt = None;
41
+ $AbortRequestedAt = None;
42
+ (* Reserved for future late-result surfacing; backend queue already rejects late timeout/cancel results. *)
43
+ $LastLateResult = None;
44
+ $LastRunStatusCellId = None;
45
+ $LastRunStatusNotebookId = None;
46
+ $LastRunStatus = None;
47
+ $LastStatus = <||>;
48
+ $LastError = None;
49
+ $PollingInProgress = False;
50
+ $MaxArtifactScanCells = 20;
51
+ $DefaultMaxCellPayloadBytes = 262144;
52
+ $MaxCellPayloadBytes = 1024 * 1024;
53
+ $BridgeHTTPTimeoutSeconds = 10;
54
+ $BridgeHTTPRetryCount = 3;
55
+ $BridgeHTTPRetryDelaySeconds = 0.25;
56
+ $BridgeInbox = {};
57
+ $ExecutorInProgress = False;
58
+ $BridgeExecutorTask = None;
59
+ $LastPollTime = None;
60
+ $LastResultStatus = None;
61
+ $DiagnosticsOpen = False;
62
+ $AgentSessionId = CreateUUID["agent-"];
63
+ $HiddenAgentTask = None;
64
+ $NotebookObjectKeys = <||>;
65
+ $AgentExecutionInProgress = False;
66
+ $HiddenAgentInProgress = False;
67
+ $MMAAgentBridgeSourceFile = If[StringQ[$InputFileName] && StringLength[$InputFileName] > 0, $InputFileName, None];
68
+ $ControlAgentNotebook = None;
69
+ $ControlAgentEvaluatorName = "MMAAgentControl";
70
+
71
+ UnixTimeMilliseconds[] := Round[1000 UnixTime[]];
72
+
73
+ NonEmptyStringQ[value_] := StringQ[value] && StringLength[value] > 0;
74
+
75
+ EnvironmentValue[name_String] := Module[{value},
76
+ value = Quiet @ Check[Environment[name], ""];
77
+ If[NonEmptyStringQ[value], value, ""]
78
+ ];
79
+
80
+ DefaultBridgeSessionFile[] := Module[{override, home},
81
+ override = EnvironmentValue["MICA_SESSION_FILE"];
82
+ If[NonEmptyStringQ[override], Return[override]];
83
+ home = EnvironmentValue["HOME"];
84
+ If[!NonEmptyStringQ[home], home = EnvironmentValue["USERPROFILE"]];
85
+ If[!NonEmptyStringQ[home], home = Directory[]];
86
+ FileNameJoin[{home, ".mica", "session.json"}]
87
+ ];
88
+
89
+ If[$BridgeSessionFile === Automatic, $BridgeSessionFile = DefaultBridgeSessionFile[]];
90
+
91
+ LoadBridgeSession[] := Module[{sessionFile = $BridgeSessionFile, payload},
92
+ If[!StringQ[sessionFile] || !FileExistsQ[sessionFile], Return[<||>]];
93
+ payload = Quiet @ Check[Import[sessionFile, "RawJSON"], $Failed];
94
+ If[AssociationQ[payload], payload, <||>]
95
+ ];
96
+
97
+ ConfigureBridgeFromSession[] := Module[{session, baseUrl, token},
98
+ session = LoadBridgeSession[];
99
+ baseUrl = Lookup[session, "baseUrl", None];
100
+ If[NonEmptyStringQ[baseUrl],
101
+ $BridgeBaseURL = baseUrl,
102
+ $BridgeBaseURL = $DefaultBridgeBaseURL
103
+ ];
104
+ token = Lookup[session, "authToken", None];
105
+ $BridgeAuthToken = If[NonEmptyStringQ[token], token, None];
106
+ $BridgeBaseURL
107
+ ];
108
+
109
+ (* $BridgePermissions replaces the plan's $Permissions name because *)
110
+ (* $Permissions is a protected Wolfram built-in symbol (Set::wrsym). *)
111
+ (* Task 7 Palette UI must bind to $BridgePermissions. *)
112
+ $DefaultBridgePermissions = <|
113
+ "ReadNotebook" -> True,
114
+ "InsertCell" -> False,
115
+ "ModifyCell" -> False,
116
+ "DeleteCell" -> False,
117
+ "RunCell" -> False,
118
+ "SaveNotebook" -> False
119
+ |>;
120
+
121
+ If[!AssociationQ[Quiet @ Check[$BridgePermissions, None]],
122
+ $BridgePermissions = $DefaultBridgePermissions
123
+ ];
124
+
125
+ PalettePermissionRow[label_String, key_String] := Row[{
126
+ Checkbox[Dynamic[$BridgePermissions[key], ($BridgePermissions[key] = #; Quiet @ Check[PostPermissions[], Null]) &]],
127
+ Spacer[8],
128
+ Style[label, 11]
129
+ }];
130
+
131
+ PaletteStatusSummary[] := Module[{server, paletteConnected, notebookAttached, pendingRequests, attachedNotebook, error},
132
+ server = Lookup[$LastStatus, "server", "unknown"];
133
+ paletteConnected = TrueQ @ Lookup[$LastStatus, "paletteConnected", False];
134
+ notebookAttached = TrueQ @ Lookup[$LastStatus, "notebookAttached", False];
135
+ pendingRequests = Max[Lookup[$LastStatus, "pendingRequests", 0], Length[$BridgeInbox]];
136
+ attachedNotebook = Lookup[$LastStatus, "attachedNotebook", <||>];
137
+ error = If[StringQ[$LastError] && StringLength[$LastError] > 0, $LastError, None];
138
+ Column[
139
+ DeleteCases[
140
+ {
141
+ Style["Server: " <> ToString[server], Bold],
142
+ Style["Palette connected: " <> If[paletteConnected, "yes", "no"], 11],
143
+ Style["Notebook attached: " <> If[notebookAttached, "yes", "no"], 11],
144
+ Style["Pending requests: " <> ToString[pendingRequests], 11],
145
+ If[AssociationQ[attachedNotebook] && attachedNotebook =!= <||>,
146
+ Style[
147
+ "Attached notebook: " <> ToString[Lookup[attachedNotebook, "notebookTitle", ""]],
148
+ 11
149
+ ],
150
+ Nothing
151
+ ],
152
+ If[error =!= None, Style["Last error: " <> ToString[error], Darker[Red]], Nothing]
153
+ },
154
+ Nothing
155
+ ],
156
+ Spacings -> 0.35
157
+ ]
158
+ ];
159
+
160
+ PaletteStatusPill[label_String, state_String:"neutral"] := Module[{background, foreground, border},
161
+ {background, foreground, border} = Which[
162
+ state === "connected", {RGBColor[0.13, 0.47, 0.27], White, RGBColor[0.20, 0.58, 0.34]},
163
+ state === "running", {RGBColor[0.16, 0.32, 0.60], White, RGBColor[0.23, 0.42, 0.76]},
164
+ state === "degraded", {RGBColor[0.72, 0.48, 0.10], White, RGBColor[0.84, 0.57, 0.14]},
165
+ state === "disconnected", {RGBColor[0.62, 0.18, 0.20], White, RGBColor[0.80, 0.26, 0.29]},
166
+ True, {RGBColor[0.24, 0.24, 0.28], White, RGBColor[0.36, 0.36, 0.40]}
167
+ ];
168
+ Framed[
169
+ Style[label, 10, Bold, foreground],
170
+ Background -> background,
171
+ FrameStyle -> border,
172
+ FrameMargins -> {{10, 10}, {4, 4}},
173
+ RoundingRadius -> 14
174
+ ]
175
+ ];
176
+
177
+ NotebookDisplayName[record_Association] := Module[{info, title, path, notebookId},
178
+ info = Lookup[record, "info", record];
179
+ notebookId = Lookup[info, "notebookId", Lookup[record, "notebookId", ""]];
180
+ title = StringTrim[ToString[Lookup[info, "notebookTitle", ""]]];
181
+ path = StringTrim[ToString[Lookup[info, "notebookPath", ""]]];
182
+ If[title === "" && path === "",
183
+ If[StringQ[notebookId] && StringLength[notebookId] > 0, notebookId, "Untitled notebook"],
184
+ If[path === "", title, title <> " — " <> FileNameTake[path]]
185
+ ]
186
+ ];
187
+
188
+ NotebookSelectorChoices[] := Module[{notebookIds = Keys[$BridgeNotebooks], record, display},
189
+ If[Length[notebookIds] > 0,
190
+ Map[
191
+ Function[notebookId,
192
+ record = NotebookRecord[notebookId];
193
+ display = NotebookDisplayName[record];
194
+ notebookId -> display
195
+ ],
196
+ notebookIds
197
+ ],
198
+ {}
199
+ ]
200
+ ];
201
+
202
+ RefreshNotebooks[] := Module[{payload, notebooks, activeNotebookId, notebookId, existing},
203
+ payload = Quiet @ Check[BridgeGet["/notebooks"], $Failed];
204
+ If[AssociationQ[payload],
205
+ notebooks = Lookup[payload, "notebooks", {}];
206
+ activeNotebookId = Lookup[payload, "activeNotebookId", $ActiveNotebookId];
207
+ If[ListQ[notebooks],
208
+ Do[
209
+ If[AssociationQ[notebook],
210
+ notebookId = Lookup[notebook, "notebookId", None];
211
+ If[StringQ[notebookId] && StringLength[notebookId] > 0,
212
+ existing = NotebookRecord[notebookId];
213
+ $BridgeNotebooks[notebookId] = Join[existing, notebook]
214
+ ]
215
+ ],
216
+ {notebook, notebooks}
217
+ ]
218
+ ];
219
+ If[StringQ[activeNotebookId] && KeyExistsQ[$BridgeNotebooks, activeNotebookId], $ActiveNotebookId = activeNotebookId]
220
+ ];
221
+ payload
222
+ ];
223
+
224
+ UseSelectedNotebook[] := Module[{notebookId = $ActiveNotebookId},
225
+ If[StringQ[notebookId] && StringLength[notebookId] > 0,
226
+ Quiet @ Check[BridgePost["/notebooks/select", <|"notebookId" -> notebookId|>], Null];
227
+ notebookId,
228
+ None
229
+ ]
230
+ ];
231
+
232
+ EnqueueBridgeRequest[request_Association] := Module[{requestId, existingRequestIds, status},
233
+ requestId = Lookup[request, "requestId", None];
234
+ If[!StringQ[requestId] || StringLength[requestId] == 0,
235
+ Return[<|"status" -> "rejected", "reason" -> "missing_requestId"|>]
236
+ ];
237
+ existingRequestIds = Lookup[$BridgeInbox, "requestId", None];
238
+ If[MemberQ[existingRequestIds, requestId],
239
+ Return[<|"status" -> "duplicate", "requestId" -> requestId, "inboxSize" -> Length[$BridgeInbox]|>]
240
+ ];
241
+ AppendTo[$BridgeInbox, request];
242
+ status = <|"status" -> "queued", "requestId" -> requestId, "inboxSize" -> Length[$BridgeInbox]|>;
243
+ status
244
+ ];
245
+
246
+ DequeueBridgeRequest[] := Module[{request},
247
+ If[Length[$BridgeInbox] == 0, Return[None]];
248
+ request = First[$BridgeInbox];
249
+ $BridgeInbox = Rest[$BridgeInbox];
250
+ request
251
+ ];
252
+
253
+ SafeExecutePendingRequest[] := Module[{request = None, result = None},
254
+ If[TrueQ[$ExecutorInProgress], Return[$LastResultStatus]];
255
+ request = DequeueBridgeRequest[];
256
+ If[request === None, Return[None]];
257
+ Internal`WithLocalSettings[
258
+ $ExecutorInProgress = True;
259
+ $CurrentRequestId = Lookup[request, "requestId", None];
260
+ result = Quiet @ Check[ExecuteRequest[request], $Failed];,
261
+ $CurrentRequestId = None;
262
+ $ExecutorInProgress = False
263
+ ];
264
+ result
265
+ ];
266
+
267
+ StartBridgeExecutor[] := Module[{task = $BridgeExecutorTask},
268
+ If[task === None || Not @ TrueQ @ Quiet @ Check[ScheduledTaskActiveQ[task], False],
269
+ $BridgeExecutorTask = RunScheduledTask[SafeExecutePendingRequest[], {0.25}]
270
+ ];
271
+ $BridgeExecutorTask
272
+ ];
273
+
274
+ RuntimeStatusCard[] := Module[{paletteConnected, transportMode, executorState, pendingRequests, runningRequest, statusState, runningRequestLabel, elapsedSeconds, runningIndicator},
275
+ paletteConnected = TrueQ @ Lookup[$LastStatus, "paletteConnected", False];
276
+ transportMode = Lookup[$LastStatus, "transportMode", "unknown"];
277
+ executorState = If[TrueQ[$ExecutorInProgress], "running", If[$BridgeExecutorTask === None, Lookup[$LastStatus, "executorState", "idle"], "idle"]];
278
+ pendingRequests = Length[$BridgeInbox];
279
+ runningRequest = Lookup[$LastStatus, "runningRequest", None];
280
+ statusState = Which[
281
+ Not[paletteConnected], "disconnected",
282
+ executorState === "running", "running",
283
+ executorState === "blocked" || executorState === "error" || pendingRequests > 0, "degraded",
284
+ True, "connected"
285
+ ];
286
+ elapsedSeconds = If[AssociationQ[runningRequest] && NumberQ[Lookup[runningRequest, "claimedAt", None]], Round[AbsoluteTime[] - Lookup[runningRequest, "claimedAt", None]], None];
287
+ runningRequestLabel = If[
288
+ AssociationQ[runningRequest],
289
+ Row[DeleteCases[
290
+ {
291
+ Style["Running request: ", Bold],
292
+ ToString[Lookup[runningRequest, "tool", "request"]],
293
+ If[StringQ[Lookup[runningRequest, "requestId", ""]], " (" <> Lookup[runningRequest, "requestId", ""] <> ")", Nothing],
294
+ If[NumberQ[elapsedSeconds], " • " <> ToString[elapsedSeconds] <> "s", Nothing]
295
+ },
296
+ Nothing
297
+ ]],
298
+ Style["Running request: none", 11]
299
+ ];
300
+ runningIndicator = If[statusState === "running", ProgressIndicator[Indeterminate, Appearance -> "Necklace"], ProgressIndicator[0, Appearance -> "Necklace"]];
301
+ Panel[
302
+ Column[
303
+ {
304
+ Grid[
305
+ {{
306
+ PaletteStatusPill[If[paletteConnected, "Connected", "Disconnected"], statusState],
307
+ PaletteStatusPill["Transport: " <> ToString[transportMode], If[transportMode === "subkernel", "connected", If[paletteConnected, "degraded", "disconnected"]]],
308
+ PaletteStatusPill["Executor: " <> ToString[executorState], statusState]
309
+ }},
310
+ Alignment -> Left,
311
+ Spacings -> {1.1, 0.8}
312
+ ],
313
+ Grid[
314
+ {{
315
+ Style["Pending requests: " <> ToString[pendingRequests], 11],
316
+ runningIndicator,
317
+ Button["Cancel Running Request", CancelCurrentRequest[], Method -> "Queued"]
318
+ }},
319
+ Alignment -> Left,
320
+ Spacings -> {2, 1}
321
+ ],
322
+ runningRequestLabel
323
+ },
324
+ Spacings -> 0.7
325
+ ],
326
+ FrameMargins -> 10,
327
+ RoundingRadius -> 8
328
+ ]
329
+ ];
330
+
331
+ PermissionsPanel[] := Panel[
332
+ Column[
333
+ {
334
+ Style["Permissions", Bold],
335
+ Grid[
336
+ {
337
+ {PalettePermissionRow["Read notebook", "ReadNotebook"]},
338
+ {PalettePermissionRow["Insert cell", "InsertCell"]},
339
+ {PalettePermissionRow["Modify cell", "ModifyCell"]},
340
+ {PalettePermissionRow["Delete cell", "DeleteCell"]},
341
+ {PalettePermissionRow["Run cell", "RunCell"]},
342
+ {PalettePermissionRow["Save notebook", "SaveNotebook"]}
343
+ },
344
+ Alignment -> Left,
345
+ Spacings -> {1, 0.35}
346
+ ]
347
+ },
348
+ Spacings -> 0.5
349
+ ],
350
+ FrameMargins -> 10,
351
+ RoundingRadius -> 8
352
+ ];
353
+
354
+ DiagnosticsPanel[] := OpenerView[
355
+ {
356
+ Style["Diagnostics", Bold],
357
+ Dynamic[
358
+ Framed[
359
+ Grid[
360
+ {
361
+ {Style["Last error", Bold], Style[If[StringQ[$LastError] && StringLength[$LastError] > 0, $LastError, "None"], 11]},
362
+ {Style["Palette id", Bold], Style[ToString[$PaletteId], 11]},
363
+ {Style["Active notebook id", Bold], Style[If[StringQ[$ActiveNotebookId], $ActiveNotebookId, "None"], 11]},
364
+ {Style["Transport", Bold], Style[ToString[Lookup[$LastStatus, "transportMode", "unknown"]], 11]},
365
+ {Style["Executor", Bold], Style[ToString[Lookup[$LastStatus, "executorState", "idle"]], 11]},
366
+ {Style["Pending requests", Bold], Style[ToString[Lookup[$LastStatus, "pendingRequests", 0]], 11]},
367
+ {Style["Running request", Bold], Style[ToString[Lookup[$LastStatus, "runningRequest", None]], 11]},
368
+ {Style["Last poll", Bold], Style[If[NumberQ[$LastPollTime], DateString[$LastPollTime], "None"], 11]},
369
+ {Style["Last result", Bold], Style[ToString[$LastResultStatus], 11]},
370
+ {Style["Inbox size", Bold], Style[ToString[Length[$BridgeInbox]], 11]}
371
+ },
372
+ Alignment -> Left,
373
+ Spacings -> {2, 0.6}
374
+ ],
375
+ FrameMargins -> 10,
376
+ RoundingRadius -> 8
377
+ ],
378
+ TrackedSymbols :> {$LastError, $PaletteId, $ActiveNotebookId, $LastStatus, $LastPollTime, $LastResultStatus, $BridgeInbox},
379
+ SynchronousUpdating -> False
380
+ ]
381
+ },
382
+ Dynamic[$DiagnosticsOpen]
383
+ ];
384
+
385
+ NotebookSelectorView[] := Module[{choices, selectedRecord, selectedInfo, selectorSetter},
386
+ selectedRecord = ActiveNotebookRecord[];
387
+ selectedInfo = Lookup[selectedRecord, "info", selectedRecord];
388
+ choices = NotebookSelectorChoices[];
389
+ selectorSetter = Function[value,
390
+ If[StringQ[value] && StringLength[value] > 0,
391
+ $ActiveNotebookId = value;
392
+ Quiet @ Check[BridgePost["/notebooks/select", <|"notebookId" -> value|>], Null]
393
+ ]
394
+ ];
395
+ Panel[
396
+ Column[
397
+ {
398
+ Grid[
399
+ {{
400
+ Style["Active notebook", Bold],
401
+ If[AssociationQ[selectedRecord] && selectedRecord =!= <||>,
402
+ PaletteStatusPill[NotebookDisplayName[selectedRecord], "connected"],
403
+ PaletteStatusPill["No notebook selected", "disconnected"]
404
+ ]
405
+ }},
406
+ Alignment -> Left,
407
+ Spacings -> {2, 0.5}
408
+ ],
409
+ If[AssociationQ[selectedRecord] && selectedRecord =!= <||>,
410
+ Grid[
411
+ {
412
+ {Style["Notebook id", Bold], Style[Lookup[selectedInfo, "notebookId", ""], 11]},
413
+ {Style["Path", Bold], Style[Lookup[selectedInfo, "notebookPath", ""], 11]}
414
+ },
415
+ Alignment -> Left,
416
+ Spacings -> {2, 0.5}
417
+ ],
418
+ Nothing
419
+ ],
420
+ If[Length[choices] > 0,
421
+ PopupMenu[Dynamic[$ActiveNotebookId, selectorSetter],
422
+ choices,
423
+ FieldSize -> Medium
424
+ ],
425
+ Style["No notebooks registered", 11, Gray]
426
+ ],
427
+ Grid[
428
+ {{
429
+ Button["Register Current Window", AttachCurrentNotebook[], Method -> "Queued"],
430
+ Button["Refresh Notebooks", RefreshNotebooks[], Method -> "Queued"],
431
+ Button["Use Selected Notebook", UseSelectedNotebook[], Method -> "Queued"],
432
+ Button["Poll now", PollBridge[], Method -> "Queued"]
433
+ }},
434
+ Alignment -> Left,
435
+ Spacings -> {1, 0.5}
436
+ ]
437
+ },
438
+ Spacings -> 0.7
439
+ ],
440
+ FrameMargins -> 10,
441
+ RoundingRadius -> 8
442
+ ]
443
+ ];
444
+
445
+ SafePollBridge[] := Module[{result = $Failed},
446
+ If[TrueQ[$PollingInProgress], Return[$LastStatus]];
447
+ Internal`WithLocalSettings[
448
+ $PollingInProgress = True,
449
+ result = Quiet @ Check[PollBridge[], $Failed],
450
+ $PollingInProgress = False
451
+ ];
452
+ result
453
+ ];
454
+
455
+ PollHeartbeat[] := Dynamic[
456
+ Refresh[
457
+ SafePollBridge[];
458
+ "",
459
+ UpdateInterval -> 1
460
+ ],
461
+ TrackedSymbols :> {},
462
+ SynchronousUpdating -> False
463
+ ];
464
+
465
+ PaletteView[] := DynamicModule[{},
466
+ Column[
467
+ {
468
+ PollHeartbeat[],
469
+ Grid[
470
+ {{
471
+ Style["MMA Agent Bridge", 16, Bold],
472
+ Dynamic[
473
+ PaletteStatusPill[
474
+ If[TrueQ @ Lookup[$LastStatus, "paletteConnected", False], "Connected", "Disconnected"],
475
+ If[TrueQ @ Lookup[$LastStatus, "paletteConnected", False], "connected", "disconnected"]
476
+ ],
477
+ TrackedSymbols :> {$LastStatus},
478
+ SynchronousUpdating -> False
479
+ ]
480
+ }},
481
+ Alignment -> Left,
482
+ Spacings -> {2, 0.5}
483
+ ],
484
+ Framed[
485
+ Column[
486
+ {
487
+ Dynamic[NotebookSelectorView[], TrackedSymbols :> {$BridgeNotebooks, $ActiveNotebookId}, SynchronousUpdating -> False],
488
+ Dynamic[RuntimeStatusCard[], TrackedSymbols :> {$LastStatus, $BridgeInbox, $ExecutorInProgress, $BridgeExecutorTask}, SynchronousUpdating -> False],
489
+ PermissionsPanel[],
490
+ DiagnosticsPanel[]
491
+ },
492
+ Spacings -> 1.1
493
+ ],
494
+ FrameMargins -> 12,
495
+ RoundingRadius -> 10
496
+ ]
497
+ },
498
+ Spacings -> 1
499
+ ]
500
+ ];
501
+
502
+ (* Re-read the small session file before each request so Wolfram follows MCP restarts and dynamic ports. *)
503
+ BridgeURL[path_String] := ConfigureBridgeFromSession[] <> path;
504
+
505
+ BridgeHeaders[] := If[
506
+ StringQ[$BridgeAuthToken] && StringLength[$BridgeAuthToken] > 0,
507
+ {"Authorization" -> "Bearer " <> $BridgeAuthToken},
508
+ {}
509
+ ];
510
+
511
+ URLComponentEncodeString[None] := "";
512
+ URLComponentEncodeString[value_] := StringReplace[
513
+ ToString[value],
514
+ {
515
+ " " -> "%20",
516
+ "!" -> "%21",
517
+ "\"" -> "%22",
518
+ "#" -> "%23",
519
+ "$" -> "%24",
520
+ "%" -> "%25",
521
+ "&" -> "%26",
522
+ "'" -> "%27",
523
+ "(" -> "%28",
524
+ ")" -> "%29",
525
+ "*" -> "%2A",
526
+ "+" -> "%2B",
527
+ "," -> "%2C",
528
+ "/" -> "%2F",
529
+ ":" -> "%3A",
530
+ ";" -> "%3B",
531
+ "<" -> "%3C",
532
+ "=" -> "%3D",
533
+ ">" -> "%3E",
534
+ "?" -> "%3F",
535
+ "@" -> "%40",
536
+ "[" -> "%5B",
537
+ "\\" -> "%5C",
538
+ "]" -> "%5D",
539
+ "^" -> "%5E",
540
+ "`" -> "%60",
541
+ "{" -> "%7B",
542
+ "|" -> "%7C",
543
+ "}" -> "%7D"
544
+ }
545
+ ];
546
+
547
+ BridgeRequestWithRetries[request_] := Module[{attempt, response = $Failed},
548
+ For[attempt = 1, attempt <= $BridgeHTTPRetryCount, attempt++,
549
+ response = Quiet @ Check[
550
+ URLRead[request, {"StatusCode", "BodyByteArray"}, TimeConstraint -> $BridgeHTTPTimeoutSeconds],
551
+ $Failed
552
+ ];
553
+ If[response =!= $Failed, Return[response]];
554
+ If[attempt < $BridgeHTTPRetryCount, Pause[$BridgeHTTPRetryDelaySeconds]]
555
+ ];
556
+ $Failed
557
+ ];
558
+
559
+ PayloadToJsonBytes[payload_Association] := ExportByteArray[payload, "RawJSON"];
560
+
561
+ JsonByteArrayToPayload[body_ByteArray] := Module[{text},
562
+ text = Quiet @ Check[ByteArrayToString[body], $Failed];
563
+ If[text === $Failed || StringLength[text] == 0, Return[<||>]];
564
+ Quiet @ Check[ImportString[text, "RawJSON"], $Failed]
565
+ ];
566
+
567
+ BridgeGet[path_String] := Module[{response},
568
+ response = BridgeRequestWithRetries[HTTPRequest[BridgeURL[path], <|"Method" -> "GET", "Headers" -> BridgeHeaders[]|>]];
569
+ If[response === $Failed, Return[$Failed]];
570
+ JsonByteArrayToPayload[response["BodyByteArray"]]
571
+ ];
572
+
573
+ BridgePost[path_String, payload_Association] := Module[{response},
574
+ response = BridgeRequestWithRetries[
575
+ HTTPRequest[
576
+ BridgeURL[path],
577
+ <|
578
+ "Method" -> "POST",
579
+ "Headers" -> BridgeHeaders[],
580
+ "ContentType" -> "application/json; charset=utf-8",
581
+ "Body" -> PayloadToJsonBytes[payload]
582
+ |>
583
+ ]
584
+ ];
585
+ If[response === $Failed, Return[$Failed]];
586
+ JsonByteArrayToPayload[response["BodyByteArray"]]
587
+ ];
588
+
589
+ PostPermissions[] := Module[{payload = <|"permissions" -> $BridgePermissions|>},
590
+ Quiet @ Check[BridgePost["/permissions", payload], $Failed]
591
+ ];
592
+
593
+ NeedsConfirmationQ[action_String] := Not @ TrueQ[$BridgePermissions[action]];
594
+
595
+ ConfirmAction[action_String, message_String] := If[
596
+ NeedsConfirmationQ[action],
597
+ ChoiceDialog[message, {"Allow" -> True, "Deny" -> False}],
598
+ True
599
+ ];
600
+
601
+ BridgeFailure[code_String, message_String] := Failure[code, <|"Message" -> message|>];
602
+
603
+ FailedRequestCode[failure_Failure] := Module[{code = failure[[1]]},
604
+ If[StringQ[code] && StringLength[code] > 0, code, "WOLFRAM_ERROR"]
605
+ ];
606
+
607
+ FailedRequestMessage[failure_Failure] := Lookup[failure[[2]], "Message", Lookup[failure[[2]], "message", "The Wolfram bridge rejected the request."]];
608
+
609
+ RequireReadPermission[] := If[
610
+ Not @ ConfirmAction["ReadNotebook", "AI requests reading the notebook. Allow?"],
611
+ $Canceled,
612
+ True
613
+ ];
614
+
615
+ NotebookIdFor[nb_NotebookObject] := Module[{existing},
616
+ existing = SelectFirst[
617
+ Keys[$BridgeNotebooks],
618
+ Function[notebookId, AssociationQ[NotebookRecord[notebookId]] && Lookup[NotebookRecord[notebookId], "notebook", None] === nb],
619
+ None
620
+ ];
621
+ If[StringQ[existing] && StringLength[existing] > 0, existing, CreateUUID["notebook-"]]
622
+ ];
623
+
624
+ NotebookInfo[nb_NotebookObject, notebookId_String] := <|
625
+ "notebookId" -> notebookId,
626
+ "notebookTitle" -> ToString @ CurrentValue[nb, WindowTitle],
627
+ "notebookPath" -> ToString @ Replace[NotebookFileName[nb], $Failed -> ""],
628
+ "wolframVersion" -> ToString @ $VersionNumber,
629
+ "platform" -> $OperatingSystem,
630
+ "paletteId" -> $PaletteId,
631
+ "permissions" -> $BridgePermissions
632
+ |>;
633
+
634
+ NotebookRecord[notebookId_String] := Lookup[$BridgeNotebooks, notebookId, <||>];
635
+
636
+ FrontendObjectKey[nb_NotebookObject] := Module[{existing},
637
+ existing = SelectFirst[Keys[$NotebookObjectKeys], Lookup[$NotebookObjectKeys, #, None] === nb &, None];
638
+ If[StringQ[existing] && StringLength[existing] > 0, existing, With[{key = CreateUUID["fe-"]}, $NotebookObjectKeys[key] = nb; key]]
639
+ ];
640
+
641
+ MeaningfulNotebookTitleQ[title_] := Module[{text = StringTrim[ToString[title]]},
642
+ StringLength[text] > 0 && !MemberQ[{"Automatic", "None", "Null"}, text]
643
+ ];
644
+
645
+ NotebookWindowTitle[nb_NotebookObject] := Module[{title},
646
+ title = Quiet @ Check[CurrentValue[nb, WindowTitle], ""];
647
+ If[MeaningfulNotebookTitleQ[title], StringTrim[ToString[title]], ""]
648
+ ];
649
+
650
+ NotebookDisplayNameForHeartbeat[nb_NotebookObject, savedPath_String, frontendObjectKey_String] := Module[{windowTitle},
651
+ windowTitle = NotebookWindowTitle[nb];
652
+ Which[
653
+ StringLength[StringTrim[savedPath]] > 0, FileNameTake[savedPath],
654
+ StringLength[windowTitle] > 0, windowTitle,
655
+ True, "Untitled notebook " <> StringTake[frontendObjectKey, -8]
656
+ ]
657
+ ];
658
+
659
+ NotebookIdForObject[nb_NotebookObject] := SelectFirst[
660
+ Keys[$BridgeNotebooks],
661
+ Function[notebookId, AssociationQ[NotebookRecord[notebookId]] && Lookup[NotebookRecord[notebookId], "notebook", None] === nb],
662
+ None
663
+ ];
664
+
665
+ ActiveNotebookRecord[] := If[StringQ[$ActiveNotebookId] && StringLength[$ActiveNotebookId] > 0, NotebookRecord[$ActiveNotebookId], <||>];
666
+
667
+ TargetNotebookId[args_Association] := Module[{explicit = Lookup[args, "notebookId", None]},
668
+ Which[
669
+ StringQ[explicit] && StringLength[explicit] > 0, explicit,
670
+ StringQ[$ActiveNotebookId] && StringLength[$ActiveNotebookId] > 0, $ActiveNotebookId,
671
+ True, None
672
+ ]
673
+ ];
674
+
675
+ RegisterCurrentNotebook[] := Module[{nb, notebookId, info, record, cellMap, nextCellId},
676
+ nb = InputNotebook[];
677
+ If[Head[nb] =!= NotebookObject, Return[$Failed]];
678
+ notebookId = NotebookIdFor[nb];
679
+ info = NotebookInfo[nb, notebookId];
680
+ record = NotebookRecord[notebookId];
681
+ cellMap = Lookup[record, "cellMap", <||>];
682
+ nextCellId = Lookup[record, "nextCellId", 1];
683
+ $AttachedNotebook = nb;
684
+ $AttachedNotebookInfo = info;
685
+ $ActiveNotebookId = notebookId;
686
+ $CellMap = cellMap;
687
+ $NextCellId = nextCellId;
688
+ $BridgeNotebooks[notebookId] = Join[
689
+ record,
690
+ <|
691
+ "notebook" -> nb,
692
+ "info" -> info,
693
+ "cellMap" -> cellMap,
694
+ "nextCellId" -> nextCellId,
695
+ "closed" -> False
696
+ |>
697
+ ];
698
+ Quiet @ Check[BridgePost["/notebooks/upsert", info], Null];
699
+ Quiet @ Check[BridgePost["/notebooks/select", <|"notebookId" -> notebookId|>], Null];
700
+ info
701
+ ];
702
+
703
+ AttachCurrentNotebook[] := RegisterCurrentNotebook[];
704
+
705
+ PostResult[requestId_String, result_Association] := Module[{payload},
706
+ payload = <|"requestId" -> requestId, "ok" -> True, "response" -> result|>;
707
+ $LastResultStatus = payload;
708
+ If[TrueQ[$AgentExecutionInProgress],
709
+ AgentPostResult[requestId, result],
710
+ BridgePost[
711
+ "/result",
712
+ <|"requestId" -> requestId, "ok" -> True, "result" -> result|>
713
+ ]
714
+ ]
715
+ ];
716
+
717
+ PostFailure[requestId_String, code_String, message_String] := Module[{payload},
718
+ payload = <|"requestId" -> requestId, "ok" -> False, "response" -> <|"code" -> code, "message" -> message|>|>;
719
+ $LastResultStatus = payload;
720
+ If[TrueQ[$AgentExecutionInProgress],
721
+ AgentPostFailure[requestId, code, message],
722
+ BridgePost[
723
+ "/result",
724
+ <|"requestId" -> requestId, "ok" -> False, "error" -> <|"code" -> code, "message" -> message|>|>
725
+ ]
726
+ ]
727
+ ];
728
+
729
+ CancelCurrentRequest[] := Module[{requestId = $CurrentRequestId, runningId = $RunningRequestId, runningCellId = $RunningCellId, runningNotebook = $RunningNotebookObject, activeId},
730
+ activeId = Which[StringQ[requestId], requestId, StringQ[runningId], runningId, True, None];
731
+ If[Head[$RunningNotebookObject] === NotebookObject,
732
+ Quiet @ Check[FrontEndTokenExecute[$RunningNotebookObject, "EvaluatorAbort"], Null],
733
+ If[$AttachedNotebook =!= None, Quiet @ Check[FrontEndTokenExecute[$AttachedNotebook, "EvaluatorAbort"], Null]]
734
+ ];
735
+ If[StringQ[activeId],
736
+ BridgePost["/cancel", <|"requestId" -> activeId, "reason" -> "USER_CANCELLED_IN_PALETTE"|>]
737
+ ]
738
+ ; If[StringQ[runningCellId] && Head[runningNotebook] === NotebookObject,
739
+ If[CellEvaluationCompleteQ[runningNotebook, runningCellId],
740
+ FinishRunningCell["finished"],
741
+ FinishRunningCell["aborted"]
742
+ ],
743
+ ClearRunningEvaluationState[];
744
+ $LastRunStatusCellId = None;
745
+ $LastRunStatusNotebookId = None;
746
+ $LastRunStatus = None;
747
+ ]
748
+ ];
749
+
750
+ StartMMAAgentPalette[] := Module[{},
751
+ StartBridgeExecutor[];
752
+ CreatePalette[PaletteView[], WindowTitle -> "MMA Agent Bridge", Saveable -> False]
753
+ ];
754
+
755
+ CellContentString[cell_CellObject] := Module[{expr, content},
756
+ expr = Quiet @ Check[NotebookRead[cell], $Failed];
757
+ If[expr === $Failed, Return[""]];
758
+ content = Replace[
759
+ expr,
760
+ {
761
+ Cell[BoxData[s_String], ___] :> s,
762
+ Cell[BoxData[boxes_], ___] :> Module[{made = Quiet @ Check[MakeExpression[boxes, StandardForm], $Failed]},
763
+ If[made === $Failed, ToString[boxes, InputForm], ToString[made, InputForm]]
764
+ ],
765
+ Cell[text_String, ___] :> text,
766
+ Cell[other_, ___] :> ToString[other, InputForm]
767
+ },
768
+ {0}
769
+ ];
770
+ If[StringQ[content], content, ToString[content, InputForm]]
771
+ ];
772
+
773
+ CellStyleName[cell_CellObject] := Module[{expr, style},
774
+ expr = Quiet @ Check[NotebookRead[cell], $Failed];
775
+ If[expr === $Failed, Return["Unknown"]];
776
+ style = Replace[expr, Cell[_, style_String, ___] :> style, {0}];
777
+ If[StringQ[style], style, "Unknown"]
778
+ ];
779
+
780
+ CellTagsList[cell_CellObject] := Module[{expr, tags},
781
+ expr = Quiet @ Check[NotebookRead[cell], $Failed];
782
+ If[expr === $Failed, Return[{}]];
783
+ tags = Replace[
784
+ expr,
785
+ {
786
+ Cell[___, CellTags -> raw_, ___] :> raw
787
+ },
788
+ {0}
789
+ ];
790
+ Replace[tags, {s_String :> {s}, l_List :> Cases[l, _String], _ -> {}}]
791
+ ];
792
+
793
+ CellPayload[cell_CellObject, id_String, index_Integer] := <|
794
+ "cellId" -> id,
795
+ "index" -> index,
796
+ "style" -> CellStyleName[cell],
797
+ "contentPreview" -> StringTake[CellContentString[cell], UpTo[240]],
798
+ "hasOutput" -> False,
799
+ "tags" -> CellTagsList[cell]
800
+ |>;
801
+
802
+ CellGeneratedBoundaryQ[style_String] := MemberQ[{"Input", "Code", "Text", "Section", "Subsection", "Subsubsection", "Title", "Chapter"}, style];
803
+
804
+ CellArtifactStyleQ[style_String] := MemberQ[{"Output", "Print", "Message"}, style];
805
+
806
+ CellPayloadMaxBytes[args_Association] := Module[{value = Lookup[args, "maxBytes", $DefaultMaxCellPayloadBytes]},
807
+ If[IntegerQ[value] && value > 0 && value <= $MaxCellPayloadBytes, value, $DefaultMaxCellPayloadBytes]
808
+ ];
809
+
810
+ Utf8LeadByteLength[byte_Integer] := Which[
811
+ byte < 128, 1,
812
+ byte < 224, 2,
813
+ byte < 240, 3,
814
+ byte < 248, 4,
815
+ True, 1
816
+ ];
817
+
818
+ Utf8PrefixByteLength[bytes_List, maxBytes_Integer] := Module[{safeLength = Min[maxBytes, Length[bytes]], start, lead, expected},
819
+ If[safeLength <= 0, Return[0]];
820
+ start = safeLength;
821
+ While[start > 1 && bytes[[start]] >= 128 && bytes[[start]] <= 191, start--];
822
+ lead = bytes[[start]];
823
+ expected = Utf8LeadByteLength[lead];
824
+ If[safeLength - start + 1 >= expected, safeLength, Max[0, start - 1]]
825
+ ];
826
+
827
+ TruncateStringToUtf8Bytes[text_String, maxBytes_Integer] := Module[{originalBytes, originalByteLength, safeLength, returnedText},
828
+ originalBytes = ToCharacterCode[text, "UTF8"];
829
+ originalByteLength = Length[originalBytes];
830
+ If[maxBytes <= 0,
831
+ Return[<|"value" -> "", "truncated" -> (originalByteLength > 0), "originalByteLength" -> originalByteLength, "returnedByteLength" -> 0|>]
832
+ ];
833
+ If[originalByteLength <= maxBytes,
834
+ Return[<|"value" -> text, "truncated" -> False, "originalByteLength" -> originalByteLength, "returnedByteLength" -> originalByteLength|>]
835
+ ];
836
+ safeLength = Utf8PrefixByteLength[originalBytes, maxBytes];
837
+ returnedText = If[safeLength > 0, ByteArrayToString[ByteArray[Take[originalBytes, safeLength]], "UTF8"], ""];
838
+ <|"value" -> returnedText, "truncated" -> True, "originalByteLength" -> originalByteLength, "returnedByteLength" -> safeLength|>
839
+ ];
840
+
841
+ TruncatePayloadFields[content_String, outputs_List, messages_List, maxBytes_Integer, includeContentQ_] := Module[{remainingByteLength = maxBytes, totalOriginalByteLength = 0, totalReturnedByteLength = 0, anyTruncated = False, truncatedContent = "", truncatedOutputs = {}, truncatedMessages = {}, processString, output, message},
842
+ processString[text_String] := Module[{item = TruncateStringToUtf8Bytes[text, remainingByteLength]},
843
+ totalOriginalByteLength += item["originalByteLength"];
844
+ totalReturnedByteLength += item["returnedByteLength"];
845
+ anyTruncated = anyTruncated || TrueQ[item["truncated"]];
846
+ remainingByteLength = Max[0, remainingByteLength - item["returnedByteLength"]];
847
+ item["value"]
848
+ ];
849
+ If[TrueQ[includeContentQ], truncatedContent = processString[content]];
850
+ Do[
851
+ AppendTo[truncatedOutputs, processString[If[StringQ[output], output, ToString[output, InputForm]]]],
852
+ {output, outputs}
853
+ ];
854
+ Do[
855
+ AppendTo[truncatedMessages, processString[If[StringQ[message], message, ToString[message, InputForm]]]],
856
+ {message, messages}
857
+ ];
858
+ Join[
859
+ If[TrueQ[includeContentQ], <|"content" -> truncatedContent|>, <||>],
860
+ <|
861
+ "outputs" -> truncatedOutputs,
862
+ "messages" -> truncatedMessages,
863
+ "truncated" -> anyTruncated,
864
+ "originalByteLength" -> totalOriginalByteLength,
865
+ "returnedByteLength" -> totalReturnedByteLength
866
+ |>
867
+ ]
868
+ ];
869
+
870
+ ArtifactId[cellId_String, kind_String, index_Integer] := cellId <> ":" <> kind <> ":" <> ToString[index];
871
+
872
+ ArtifactDescriptor[cellId_String, kind_String, index_Integer, text_String, maxBytes_Integer] := Module[{byteLength, preview},
873
+ byteLength = Length[ToCharacterCode[text, "UTF8"]];
874
+ preview = TruncateStringToUtf8Bytes[text, maxBytes];
875
+ <|
876
+ "artifactId" -> ArtifactId[cellId, kind, index],
877
+ "type" -> kind,
878
+ "index" -> index,
879
+ "byteLength" -> byteLength,
880
+ "preview" -> preview["value"],
881
+ "previewByteLength" -> preview["returnedByteLength"],
882
+ "truncated" -> True
883
+ |>
884
+ ];
885
+
886
+ ArtifactPayloadFields[cellId_String, outputs_List, messages_List, maxBytes_Integer] := Module[{remainingByteLength = maxBytes, totalOriginalByteLength = 0, totalReturnedByteLength = 0, anyTruncated = False, processedOutputs = {}, processedMessages = {}, processString, output, message},
887
+ processString[text_, kind_String, index_Integer] := Module[{value = If[StringQ[text], text, ToString[text, InputForm]], byteLength, descriptor},
888
+ byteLength = Length[ToCharacterCode[value, "UTF8"]];
889
+ totalOriginalByteLength += byteLength;
890
+ If[byteLength <= remainingByteLength,
891
+ totalReturnedByteLength += byteLength;
892
+ remainingByteLength = Max[0, remainingByteLength - byteLength];
893
+ value,
894
+ anyTruncated = True;
895
+ descriptor = ArtifactDescriptor[cellId, kind, index, value, remainingByteLength];
896
+ totalReturnedByteLength += descriptor["previewByteLength"];
897
+ remainingByteLength = Max[0, remainingByteLength - descriptor["previewByteLength"]];
898
+ descriptor
899
+ ]
900
+ ];
901
+ Do[
902
+ AppendTo[processedOutputs, processString[outputs[[i]], "output", i - 1]],
903
+ {i, Length[outputs]}
904
+ ];
905
+ Do[
906
+ AppendTo[processedMessages, processString[messages[[i]], "message", i - 1]],
907
+ {i, Length[messages]}
908
+ ];
909
+ <|
910
+ "outputs" -> processedOutputs,
911
+ "messages" -> processedMessages,
912
+ "truncated" -> anyTruncated,
913
+ "originalByteLength" -> totalOriginalByteLength,
914
+ "returnedByteLength" -> totalReturnedByteLength
915
+ |>
916
+ ];
917
+
918
+ Utf8SliceStringToBytes[text_String, offset_Integer, limit_Integer] := Module[{bytes, originalByteLength, start, available, safeLength, sliceBytes, value},
919
+ bytes = ToCharacterCode[text, "UTF8"];
920
+ originalByteLength = Length[bytes];
921
+ If[limit <= 0 || offset >= originalByteLength,
922
+ Return[<|"value" -> "", "originalByteLength" -> originalByteLength, "returnedByteLength" -> 0, "nextOffset" -> originalByteLength|>]
923
+ ];
924
+ start = Max[1, offset + 1];
925
+ While[start <= originalByteLength && bytes[[start]] >= 128 && bytes[[start]] <= 191, start++];
926
+ available = originalByteLength - start + 1;
927
+ If[available <= 0,
928
+ Return[<|"value" -> "", "originalByteLength" -> originalByteLength, "returnedByteLength" -> 0, "nextOffset" -> originalByteLength|>]
929
+ ];
930
+ safeLength = Utf8PrefixByteLength[Take[bytes, {start, originalByteLength}], Min[limit, available]];
931
+ sliceBytes = If[safeLength > 0, Take[bytes, {start, start + safeLength - 1}], {}];
932
+ value = If[safeLength > 0, ByteArrayToString[ByteArray[sliceBytes], "UTF8"], ""];
933
+ <|"value" -> value, "originalByteLength" -> originalByteLength, "returnedByteLength" -> safeLength, "nextOffset" -> (start - 1 + safeLength)|>
934
+ ];
935
+
936
+ CellEvaluationTaggingPath[cellId_String] := {TaggingRules, "MMAAgentBridge", "evaluations", cellId, "complete"};
937
+
938
+ MarkCellEvaluationComplete[cellId_String] := Quiet @ Check[
939
+ CurrentValue[EvaluationNotebook[], CellEvaluationTaggingPath[cellId]] = True,
940
+ Null
941
+ ];
942
+
943
+ ClearCellEvaluationComplete[notebook_NotebookObject, cellId_String] := Quiet @ Check[
944
+ CurrentValue[notebook, CellEvaluationTaggingPath[cellId]] = Inherited,
945
+ Null
946
+ ];
947
+
948
+ CellEvaluationCompleteQ[notebook_NotebookObject, cellId_String] := TrueQ @ Quiet @ Check[
949
+ CurrentValue[notebook, CellEvaluationTaggingPath[cellId]],
950
+ False
951
+ ];
952
+
953
+ CellEpilogOptionRule[cell_CellObject] := Module[{heldOptions, matches},
954
+ heldOptions = Apply[HoldComplete, Quiet @ Check[Options[cell, CellEpilog], {}]];
955
+ matches = Cases[heldOptions, HoldPattern[rule : ((CellEpilog -> _) | (CellEpilog :> _))] :> HoldComplete[rule], Infinity, 1];
956
+ If[Length[matches] > 0, First[matches], HoldComplete[CellEpilog -> Inherited]]
957
+ ];
958
+
959
+ CellEffectiveEpilogOptionRule[cell_CellObject] := Module[{restoreRule, effective},
960
+ restoreRule = CellEpilogOptionRule[cell];
961
+ If[restoreRule =!= HoldComplete[CellEpilog -> Inherited], Return[restoreRule]];
962
+ (* Inherited CellEpilog is captured as an effective value before installing the bridge epilog. *)
963
+ effective = Quiet @ Check[CurrentValue[cell, CellEpilog], Inherited];
964
+ HoldComplete[CellEpilog :> effective]
965
+ ];
966
+
967
+ RunOriginalCellEpilog[HoldComplete[CellEpilog -> None]] := Null;
968
+ RunOriginalCellEpilog[HoldComplete[CellEpilog -> Inherited]] := Null;
969
+ RunOriginalCellEpilog[HoldComplete[CellEpilog :> None]] := Null;
970
+ RunOriginalCellEpilog[HoldComplete[CellEpilog :> Inherited]] := Null;
971
+ RunOriginalCellEpilog[HoldComplete[CellEpilog -> value_]] := value;
972
+ RunOriginalCellEpilog[HoldComplete[CellEpilog :> value_]] := value;
973
+ RunOriginalCellEpilog[_] := Null;
974
+
975
+ RestoreCellEpilogOption[cell_CellObject, HoldComplete[CellEpilog -> Inherited]] := Quiet @ Check[SetOptions[cell, CellEpilog -> Inherited], Null];
976
+ RestoreCellEpilogOption[cell_CellObject, HoldComplete[CellEpilog -> None]] := Quiet @ Check[SetOptions[cell, CellEpilog -> None], Null];
977
+ RestoreCellEpilogOption[cell_CellObject, HoldComplete[CellEpilog :> value_]] := Quiet @ Check[SetOptions[cell, CellEpilog :> value], Null];
978
+ (* Immediate CellEpilog rules have already evaluated by the time Options returns them; restore via RuleDelayed to avoid restore-time side effects. *)
979
+ RestoreCellEpilogOption[cell_CellObject, HoldComplete[CellEpilog -> value_]] := Quiet @ Check[SetOptions[cell, CellEpilog :> Unevaluated[value]], Null];
980
+ RestoreCellEpilogOption[_, _] := Null;
981
+
982
+ InstallRunningCellEpilog[cell_CellObject, cellId_String] := Module[{originalEpilogRule, restoreEpilogRule, result},
983
+ restoreEpilogRule = CellEpilogOptionRule[cell];
984
+ originalEpilogRule = CellEffectiveEpilogOptionRule[cell];
985
+ result = Quiet @ Check[
986
+ With[{targetCellId = cellId, heldOriginalEpilogRule = originalEpilogRule},
987
+ SetOptions[cell, CellEpilog :> Internal`WithLocalSettings[
988
+ Null,
989
+ RunOriginalCellEpilog[heldOriginalEpilogRule],
990
+ MarkCellEvaluationComplete[targetCellId]
991
+ ]]
992
+ ],
993
+ $Failed
994
+ ];
995
+ If[result === $Failed, Return[BridgeFailure["RUN_FAILED", "The FrontEnd failed to install the completion tracker."]]];
996
+ $RunningCellObject = cell;
997
+ $RunningCellOriginalEpilogRule = originalEpilogRule;
998
+ $RunningCellRestoreEpilogRule = restoreEpilogRule;
999
+ True
1000
+ ];
1001
+
1002
+ RestoreRunningCellEpilog[] := Module[{},
1003
+ If[Head[$RunningCellObject] === CellObject,
1004
+ RestoreCellEpilogOption[$RunningCellObject, $RunningCellRestoreEpilogRule]
1005
+ ];
1006
+ $RunningCellObject = None;
1007
+ $RunningCellOriginalEpilogRule = HoldComplete[CellEpilog -> Inherited];
1008
+ $RunningCellRestoreEpilogRule = HoldComplete[CellEpilog -> Inherited];
1009
+ ];
1010
+
1011
+ ClearRunningEvaluationState[] := Module[{cellId = $RunningCellId, notebook = $RunningNotebookObject},
1012
+ RestoreRunningCellEpilog[];
1013
+ If[Head[notebook] === NotebookObject && StringQ[cellId], ClearCellEvaluationComplete[notebook, cellId]];
1014
+ $RunningCellId = None;
1015
+ $RunningRequestId = None;
1016
+ $RunningNotebookId = None;
1017
+ $RunningNotebookObject = None;
1018
+ $RunningStartedAt = None;
1019
+ $RunningStatus = None;
1020
+ $AbortRequestedAt = None;
1021
+ $RunningTimeoutAt = None
1022
+ ];
1023
+
1024
+ FinishRunningCell[status_String] := Module[{cellId = $RunningCellId, notebookId = $RunningNotebookId},
1025
+ ClearRunningEvaluationState[];
1026
+ $LastRunStatusCellId = cellId;
1027
+ $LastRunStatusNotebookId = notebookId;
1028
+ $RunningStatus = status;
1029
+ $LastRunStatus = status
1030
+ ];
1031
+
1032
+ CellGeneratedArtifactQ[cell_CellObject] := TrueQ[Quiet @ Check[CurrentValue[cell, GeneratedCell], False]] && CellArtifactStyleQ[CellStyleName[cell]];
1033
+
1034
+ CellOwnsGeneratedArtifactsQ[style_String] := MemberQ[{"Input", "Code"}, style];
1035
+
1036
+ GeneratedArtifactsAfterCell[cell_CellObject, notebook_:Automatic] := Module[{nb, cells, position, following},
1037
+ nb = If[notebook === Automatic, $AttachedNotebook, notebook];
1038
+ If[nb === None, Return[{}]];
1039
+ If[!CellOwnsGeneratedArtifactsQ[CellStyleName[cell]], Return[{}]];
1040
+ cells = Cells[nb];
1041
+ position = FirstPosition[cells, cell, Missing["NotFound"]];
1042
+ If[MissingQ[position], Return[{}]];
1043
+ following = Take[cells, {First[position] + 1, Min[Length[cells], First[position] + $MaxArtifactScanCells]}];
1044
+ TakeWhile[following, CellGeneratedArtifactQ]
1045
+ ];
1046
+
1047
+ CheckRunningTimeout[] := Module[{},
1048
+ If[
1049
+ StringQ[$RunningCellId] && NumberQ[$RunningTimeoutAt] && AbsoluteTime[] >= $RunningTimeoutAt,
1050
+ Quiet @ Check[
1051
+ If[Head[$RunningNotebookObject] === NotebookObject,
1052
+ FrontEndTokenExecute[$RunningNotebookObject, "EvaluatorAbort"],
1053
+ If[$AttachedNotebook =!= None, FrontEndTokenExecute[$AttachedNotebook, "EvaluatorAbort"], Null]
1054
+ ],
1055
+ Null
1056
+ ];
1057
+ FinishRunningCell["timeout"]
1058
+ ]
1059
+ ];
1060
+
1061
+ CellArtifactScan[cell_CellObject, cellId_String:"", notebook_:Automatic] := Module[{nb, cells, position, following, artifacts, outputs = {}, messages = {}, current, style, content, status, sameRunningCellQ, hasArtifactsQ, hasFinalOutputQ, evaluationCompleteQ},
1062
+ CheckRunningTimeout[];
1063
+ nb = If[notebook === Automatic, $AttachedNotebook, notebook];
1064
+ If[nb === None, Return[<|"outputs" -> {}, "messages" -> {}, "status" -> "unknown"|>]];
1065
+ If[!CellOwnsGeneratedArtifactsQ[CellStyleName[cell]], Return[<|"outputs" -> {}, "messages" -> {}, "status" -> "unknown"|>]];
1066
+ cells = Cells[nb];
1067
+ position = FirstPosition[cells, cell, Missing["NotFound"]];
1068
+ If[MissingQ[position], Return[<|"outputs" -> {}, "messages" -> {}, "status" -> "unknown"|>]];
1069
+ following = Take[cells, {First[position] + 1, Min[Length[cells], First[position] + $MaxArtifactScanCells]}];
1070
+ artifacts = TakeWhile[following, CellArtifactStyleQ[CellStyleName[#]] &];
1071
+ Do[
1072
+ current = artifacts[[i]];
1073
+ style = CellStyleName[current];
1074
+ content = CellContentString[current];
1075
+ Which[
1076
+ style === "Output" || style === "Print", AppendTo[outputs, content],
1077
+ style === "Message", AppendTo[messages, content]
1078
+ ];
1079
+ , {i, Length[artifacts]}
1080
+ ];
1081
+ sameRunningCellQ = StringQ[cellId] && StringQ[$RunningCellId] && cellId === $RunningCellId && (notebook === Automatic || nb === $RunningNotebookObject);
1082
+ hasArtifactsQ = Length[outputs] > 0 || Length[messages] > 0;
1083
+ hasFinalOutputQ = MemberQ[CellStyleName /@ artifacts, "Output"];
1084
+ evaluationCompleteQ = StringQ[cellId] && CellEvaluationCompleteQ[nb, cellId];
1085
+ status = Which[
1086
+ StringQ[cellId] && StringQ[$LastRunStatusCellId] && cellId === $LastRunStatusCellId && StringQ[$LastRunStatusNotebookId] && NotebookIdForObject[nb] === $LastRunStatusNotebookId && $LastRunStatus === "timeout",
1087
+ "timeout",
1088
+ StringQ[cellId] && StringQ[$LastRunStatusCellId] && cellId === $LastRunStatusCellId && StringQ[$LastRunStatusNotebookId] && NotebookIdForObject[nb] === $LastRunStatusNotebookId && $LastRunStatus === "finished",
1089
+ "finished",
1090
+ StringQ[cellId] && StringQ[$LastRunStatusCellId] && cellId === $LastRunStatusCellId && StringQ[$LastRunStatusNotebookId] && NotebookIdForObject[nb] === $LastRunStatusNotebookId && $LastRunStatus === "aborted",
1091
+ "aborted",
1092
+ sameRunningCellQ && NumberQ[$AbortRequestedAt] && !evaluationCompleteQ,
1093
+ "abort_requested",
1094
+ sameRunningCellQ && NumberQ[$AbortRequestedAt] && evaluationCompleteQ,
1095
+ (FinishRunningCell["aborted"]; "aborted"),
1096
+ sameRunningCellQ && NumberQ[$RunningStartedAt] && (AbsoluteTime[] - $RunningStartedAt < $RunningStatusGraceSeconds),
1097
+ "running",
1098
+ sameRunningCellQ && (evaluationCompleteQ || hasFinalOutputQ),
1099
+ (FinishRunningCell["finished"]; "finished"),
1100
+ sameRunningCellQ,
1101
+ "running",
1102
+ evaluationCompleteQ || hasFinalOutputQ || hasArtifactsQ, "finished",
1103
+ True, "unknown"
1104
+ ];
1105
+ <|
1106
+ "outputs" -> outputs,
1107
+ "messages" -> messages,
1108
+ "status" -> status
1109
+ |>
1110
+ ];
1111
+
1112
+ RefreshCellMap[notebookId_String] := Module[{record, nb, cells, idByCell, previousIds, id, payload, cellMap, nextCellId},
1113
+ record = NotebookRecord[notebookId];
1114
+ If[!AssociationQ[record] || record === <||>, Return[BridgeFailure["BAD_REQUEST", "No notebook is registered."]]];
1115
+ nb = Lookup[record, "notebook", None];
1116
+ If[Head[nb] =!= NotebookObject, Return[BridgeFailure["BAD_REQUEST", "Notebook is unavailable."]]];
1117
+ cells = Quiet @ Check[Cells[nb], $Failed];
1118
+ If[cells === $Failed, Return[BridgeFailure["BAD_REQUEST", "Notebook is unavailable."]]];
1119
+ previousIds = AssociationThread[Values[Lookup[record, "cellMap", <||>]], Keys[Lookup[record, "cellMap", <||>]]];
1120
+ idByCell = Association[];
1121
+ nextCellId = Lookup[record, "nextCellId", 1];
1122
+ Do[
1123
+ id = Lookup[previousIds, cell, Missing["NotFound"]];
1124
+ If[MissingQ[id], id = "cell_" <> ToString[nextCellId++]];
1125
+ AssociateTo[idByCell, cell -> id];
1126
+ , {cell, cells}
1127
+ ];
1128
+ cellMap = AssociationThread[Values[idByCell], Keys[idByCell]];
1129
+ payload = MapIndexed[CellPayload[#1, Lookup[idByCell, #1], First[#2]] &, cells];
1130
+ $BridgeNotebooks[notebookId] = Join[record, <|"cellMap" -> cellMap, "nextCellId" -> nextCellId, "closed" -> False|>];
1131
+ If[$ActiveNotebookId === notebookId,
1132
+ $AttachedNotebook = nb;
1133
+ $AttachedNotebookInfo = Lookup[$BridgeNotebooks[notebookId], "info", <||>];
1134
+ $CellMap = cellMap;
1135
+ $NextCellId = nextCellId;
1136
+ ];
1137
+ payload
1138
+ ];
1139
+
1140
+ ReadCellById[args_Association] := Module[{notebookId, record, cellId, cell, maxBytes, payload},
1141
+ If[RequireReadPermission[] === $Canceled, Return[$Canceled]];
1142
+ notebookId = TargetNotebookId[args];
1143
+ If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1144
+ record = NotebookRecord[notebookId];
1145
+ If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1146
+ cellId = Lookup[args, "cellId", ""];
1147
+ If[StringLength[cellId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "cellId is required."|>]]];
1148
+ cell = Lookup[Lookup[record, "cellMap", <||>], cellId, Missing["NotFound"]];
1149
+ If[MissingQ[cell], Return[Failure["BAD_REQUEST", <|"message" -> "Requested cell was not found."|>]]];
1150
+ maxBytes = CellPayloadMaxBytes[args];
1151
+ With[{artifacts = CellArtifactScan[cell, cellId, Lookup[record, "notebook", None]]},
1152
+ payload = TruncatePayloadFields[CellContentString[cell], artifacts["outputs"], artifacts["messages"], maxBytes, True];
1153
+ Join[
1154
+ <|"cellId" -> cellId, "style" -> CellStyleName[cell]|>,
1155
+ payload,
1156
+ <|"status" -> artifacts["status"]|>
1157
+ ]
1158
+ ]
1159
+ ];
1160
+
1161
+ GetCellOutputById[args_Association] := Module[{notebookId, record, cellId, cell, artifacts, maxBytes, payload},
1162
+ If[RequireReadPermission[] === $Canceled, Return[$Canceled]];
1163
+ notebookId = TargetNotebookId[args];
1164
+ If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1165
+ record = NotebookRecord[notebookId];
1166
+ If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1167
+ cellId = Lookup[args, "cellId", ""];
1168
+ If[StringLength[cellId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "cellId is required."|>]]];
1169
+ cell = Lookup[Lookup[record, "cellMap", <||>], cellId, Missing["NotFound"]];
1170
+ If[MissingQ[cell], Return[Failure["BAD_REQUEST", <|"message" -> "Requested cell was not found."|>]]];
1171
+ maxBytes = CellPayloadMaxBytes[args];
1172
+ artifacts = CellArtifactScan[cell, cellId, Lookup[record, "notebook", None]];
1173
+ payload = ArtifactPayloadFields[cellId, artifacts["outputs"], artifacts["messages"], maxBytes];
1174
+ Join[
1175
+ <|"cellId" -> cellId|>,
1176
+ payload,
1177
+ <|"status" -> artifacts["status"]|>
1178
+ ]
1179
+ ];
1180
+
1181
+ ReadArtifactById[args_Association] := Module[{notebookId, record, artifactId, parts, cellId, kind, indexText, index, cell, artifacts, artifactList, text, offset, limit, page, nextOffset, done},
1182
+ If[RequireReadPermission[] === $Canceled, Return[$Canceled]];
1183
+ notebookId = TargetNotebookId[args];
1184
+ If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1185
+ record = NotebookRecord[notebookId];
1186
+ If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1187
+ artifactId = Lookup[args, "artifactId", ""];
1188
+ If[!StringQ[artifactId] || StringLength[artifactId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "artifactId is required."|>]]];
1189
+ parts = StringSplit[artifactId, ":"];
1190
+ If[Length[parts] =!= 3, Return[Failure["BAD_REQUEST", <|"message" -> "artifactId must have the form cellId:type:index."|>]]];
1191
+ {cellId, kind, indexText} = parts;
1192
+ If[!MemberQ[{"output", "message"}, kind], Return[Failure["BAD_REQUEST", <|"message" -> "artifact type must be output or message."|>]]];
1193
+ If[!StringMatchQ[indexText, DigitCharacter..], Return[Failure["BAD_REQUEST", <|"message" -> "artifact index must be a non-negative integer."|>]]];
1194
+ index = ToExpression[indexText];
1195
+ offset = Lookup[args, "offset", 0];
1196
+ limit = Lookup[args, "limit", 65536];
1197
+ If[!IntegerQ[offset] || offset < 0, Return[Failure["BAD_REQUEST", <|"message" -> "offset must be a non-negative integer."|>]]];
1198
+ If[!IntegerQ[limit] || limit <= 0 || limit > $MaxCellPayloadBytes, Return[Failure["BAD_REQUEST", <|"message" -> "limit must be a positive integer up to 1 MiB."|>]]];
1199
+ cell = Lookup[Lookup[record, "cellMap", <||>], cellId, Missing["NotFound"]];
1200
+ If[MissingQ[cell], Return[Failure["BAD_REQUEST", <|"message" -> "Requested cell was not found."|>]]];
1201
+ artifacts = CellArtifactScan[cell, cellId, Lookup[record, "notebook", None]];
1202
+ artifactList = If[kind === "output", artifacts["outputs"], artifacts["messages"]];
1203
+ If[index >= Length[artifactList], Return[Failure["BAD_REQUEST", <|"message" -> "Requested artifact was not found."|>]]];
1204
+ text = artifactList[[index + 1]];
1205
+ page = Utf8SliceStringToBytes[text, offset, limit];
1206
+ nextOffset = page["nextOffset"];
1207
+ done = nextOffset >= page["originalByteLength"];
1208
+ <|
1209
+ "artifactId" -> artifactId,
1210
+ "offset" -> offset,
1211
+ "limit" -> limit,
1212
+ "data" -> page["value"],
1213
+ "nextOffset" -> nextOffset,
1214
+ "done" -> done,
1215
+ "byteLength" -> page["originalByteLength"]
1216
+ |>
1217
+ ];
1218
+
1219
+ MakeCellExpression[content_String, style_String] := Module[{cellStyle = If[StringQ[style] && StringLength[style] > 0, style, "Input"]},
1220
+ If[cellStyle === "Input",
1221
+ Cell[BoxData[content], "Input", CellTags -> {"AI-Generated"}],
1222
+ Cell[content, cellStyle, CellTags -> {"AI-Generated"}]
1223
+ ]
1224
+ ];
1225
+
1226
+ InsertCellAtLocation[notebook_NotebookObject, anchor_CellObject, newCell_] := Module[{beforeCount, writeResult, afterCount},
1227
+ beforeCount = Length[Cells[notebook]];
1228
+ writeResult = Quiet @ Check[
1229
+ If[
1230
+ NameQ["System`NotebookLocationSpecifier"],
1231
+ NotebookWrite[NotebookLocationSpecifier[anchor, "After"], newCell, None],
1232
+ SelectionMove[anchor, After, Cell];
1233
+ NotebookWrite[notebook, newCell, None]
1234
+ ],
1235
+ $Failed
1236
+ ];
1237
+ If[writeResult === $Failed, Return[BridgeFailure["INSERT_FAILED", "The FrontEnd failed to insert the cell."]]];
1238
+ afterCount = Length[Cells[notebook]];
1239
+ If[afterCount <= beforeCount,
1240
+ Return[BridgeFailure["INSERT_FAILED", "The FrontEnd did not add a new cell; refusing to report a replacement as insertion."]]
1241
+ ];
1242
+ True
1243
+ ];
1244
+
1245
+ InsertCellAtBeginning[notebook_NotebookObject, newCell_] := Module[{beforeCount, writeResult, afterCount},
1246
+ beforeCount = Length[Cells[notebook]];
1247
+ SelectionMove[notebook, Before, Notebook];
1248
+ writeResult = Quiet @ Check[NotebookWrite[notebook, newCell, None], $Failed];
1249
+ If[writeResult === $Failed, Return[BridgeFailure["INSERT_FAILED", "The FrontEnd failed to insert the first cell."]]];
1250
+ afterCount = Length[Cells[notebook]];
1251
+ If[afterCount <= beforeCount,
1252
+ Return[BridgeFailure["INSERT_FAILED", "The FrontEnd did not add the first cell."]]
1253
+ ];
1254
+ True
1255
+ ];
1256
+
1257
+ InsertCellRequest[args_Association] := Module[{notebookId, record, afterId, style, content, newCell, notebook, anchor, cells, inserted, refreshed},
1258
+ If[Not @ ConfirmAction["InsertCell", "AI requests inserting 1 cell. Allow?"], Return[$Canceled]];
1259
+ notebookId = TargetNotebookId[args];
1260
+ If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1261
+ record = NotebookRecord[notebookId];
1262
+ If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1263
+ notebook = Lookup[record, "notebook", None];
1264
+ If[Head[notebook] =!= NotebookObject, Return[Failure["BAD_REQUEST", <|"message" -> "Notebook is unavailable."|>]]];
1265
+ afterId = Lookup[args, "afterCellId", None];
1266
+ If[afterId === "__end__", afterId = None];
1267
+ style = Lookup[args, "style", "Input"];
1268
+ content = Lookup[args, "content", ""];
1269
+ If[!StringQ[content], Return[Failure["BAD_REQUEST", <|"Message" -> "Cell content must be a string."|>]]];
1270
+ newCell = MakeCellExpression[content, style];
1271
+ cells = Cells[notebook];
1272
+ If[StringQ[afterId] && Length[cells] == 0,
1273
+ inserted = InsertCellAtBeginning[notebook, newCell],
1274
+ If[StringQ[afterId],
1275
+ If[!KeyExistsQ[Lookup[record, "cellMap", <||>], afterId], Return[Failure["BAD_REQUEST", <|"Message" -> StringTemplate["Unknown afterCellId ``."][afterId]|>]]];
1276
+ anchor = Lookup[record, "cellMap", <||>][afterId];
1277
+ inserted = InsertCellAtLocation[notebook, anchor, newCell],
1278
+ If[Length[cells] > 0,
1279
+ inserted = InsertCellAtLocation[notebook, Last[cells], newCell],
1280
+ inserted = InsertCellAtBeginning[notebook, newCell]
1281
+ ]
1282
+ ]
1283
+ ];
1284
+ If[MatchQ[inserted, _Failure], Return[inserted]];
1285
+ refreshed = Quiet @ Check[RefreshCellMap[notebookId], $Failed];
1286
+ If[refreshed === $Failed,
1287
+ $BridgeNotebooks[notebookId] = Join[record, <|"closed" -> False|>];
1288
+ <|"status" -> "inserted"|>,
1289
+ <|"status" -> "inserted", "cells" -> refreshed|>
1290
+ ]
1291
+ ];
1292
+
1293
+ ModifyCellRequest[args_Association] := Module[{notebookId, record, notebook, cellId, cellMap, cell, content, style, newCell, cells, cellIndex, updatedCells, updatedCell, writeResult},
1294
+ If[Not @ ConfirmAction["ModifyCell", "AI requests modifying 1 cell. Allow?"], Return[$Canceled]];
1295
+ notebookId = TargetNotebookId[args];
1296
+ If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1297
+ record = NotebookRecord[notebookId];
1298
+ If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1299
+ notebook = Lookup[record, "notebook", None];
1300
+ If[Head[notebook] =!= NotebookObject, Return[Failure["BAD_REQUEST", <|"message" -> "Notebook is unavailable."|>]]];
1301
+ cellId = Lookup[args, "cellId", ""];
1302
+ If[!StringQ[cellId] || StringLength[cellId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "cellId is required."|>]]];
1303
+ cellMap = Lookup[record, "cellMap", <||>];
1304
+ cell = Lookup[cellMap, cellId, Missing["NotFound"]];
1305
+ If[MissingQ[cell], Return[Failure["BAD_REQUEST", <|"message" -> "Requested cell was not found."|>]]];
1306
+ cells = Cells[notebook];
1307
+ cellIndex = FirstPosition[cells, cell, Missing["NotFound"]];
1308
+ If[MissingQ[cellIndex], Return[Failure["BAD_REQUEST", <|"message" -> "Requested cell was not found."|>]]];
1309
+ style = CellStyleName[cell];
1310
+ content = Lookup[args, "content", ""];
1311
+ If[!StringQ[content], Return[Failure["BAD_REQUEST", <|"message" -> "Cell content must be a string."|>]]];
1312
+ newCell = MakeCellExpression[content, style];
1313
+ writeResult = Quiet @ Check[NotebookWrite[cell, newCell], $Failed];
1314
+ If[writeResult === $Failed, Return[Failure["MODIFY_FAILED", <|"message" -> "The FrontEnd failed to modify the cell."|>]]];
1315
+ updatedCells = Cells[notebook];
1316
+ If[First[cellIndex] > Length[updatedCells], Return[Failure["MODIFY_FAILED", <|"message" -> "The FrontEnd did not return the modified cell."|>]]];
1317
+ updatedCell = updatedCells[[First[cellIndex]]];
1318
+ AssociateTo[cellMap, cellId -> updatedCell];
1319
+ $BridgeNotebooks[notebookId] = Join[record, <|"cellMap" -> cellMap, "closed" -> False|>];
1320
+ If[$ActiveNotebookId === notebookId, $CellMap = cellMap];
1321
+ <|"status" -> "modified", "cellId" -> cellId|>
1322
+ ];
1323
+
1324
+ DeleteCellRequest[args_Association] := Module[{notebookId, record, notebook, cellId, cell, cellMap, artifacts, artifactIds, newCellMap},
1325
+ If[Not @ ConfirmAction["DeleteCell", "AI requests deleting 1 cell. Allow?"], Return[$Canceled]];
1326
+ notebookId = TargetNotebookId[args];
1327
+ If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1328
+ record = NotebookRecord[notebookId];
1329
+ If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1330
+ notebook = Lookup[record, "notebook", None];
1331
+ If[Head[notebook] =!= NotebookObject, Return[Failure["BAD_REQUEST", <|"message" -> "Notebook is unavailable."|>]]];
1332
+ cellId = Lookup[args, "cellId", ""];
1333
+ If[!StringQ[cellId] || StringLength[cellId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "cellId is required."|>]]];
1334
+ cellMap = Lookup[record, "cellMap", <||>];
1335
+ cell = Lookup[cellMap, cellId, Missing["NotFound"]];
1336
+ If[MissingQ[cell], Return[Failure["BAD_REQUEST", <|"message" -> "Requested cell was not found."|>]]];
1337
+ artifacts = GeneratedArtifactsAfterCell[cell, notebook];
1338
+ artifactIds = Keys @ Select[cellMap, MemberQ[artifacts, #] &];
1339
+ Scan[NotebookDelete, Reverse[artifacts]];
1340
+ NotebookDelete[cell];
1341
+ newCellMap = KeyDrop[cellMap, Join[{cellId}, artifactIds]];
1342
+ $BridgeNotebooks[notebookId] = Join[record, <|"cellMap" -> newCellMap, "closed" -> False|>];
1343
+ If[$ActiveNotebookId === notebookId, $CellMap = newCellMap];
1344
+ <|"status" -> "deleted", "cellId" -> cellId, "deletedArtifactCount" -> Length[artifacts]|>
1345
+ ];
1346
+
1347
+ RunCellRequest[args_Association] := Module[{notebookId, record, notebook, cellId, cell, timeoutSec = Lookup[args, "timeoutSec", 120], installedEpilog, evaluateResult},
1348
+ If[Not @ ConfirmAction["RunCell", "AI requests running 1 cell. Allow?"], Return[$Canceled]];
1349
+ notebookId = TargetNotebookId[args];
1350
+ If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1351
+ record = NotebookRecord[notebookId];
1352
+ If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1353
+ notebook = Lookup[record, "notebook", None];
1354
+ If[Head[notebook] =!= NotebookObject, Return[Failure["BAD_REQUEST", <|"message" -> "Notebook is unavailable."|>]]];
1355
+ cellId = Lookup[args, "cellId", ""];
1356
+ If[!StringQ[cellId] || StringLength[cellId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "cellId is required."|>]]];
1357
+ cell = Lookup[Lookup[record, "cellMap", <||>], cellId, Missing["NotFound"]];
1358
+ If[MissingQ[cell], Return[Failure["BAD_REQUEST", <|"message" -> "Requested cell was not found."|>]]];
1359
+ ClearCellEvaluationComplete[notebook, cellId];
1360
+ installedEpilog = InstallRunningCellEpilog[cell, cellId];
1361
+ If[MatchQ[installedEpilog, _Failure], Return[installedEpilog]];
1362
+ $LastRunStatusCellId = None;
1363
+ $LastRunStatusNotebookId = None;
1364
+ $LastRunStatus = None;
1365
+ $RunningStatus = "running";
1366
+ $AbortRequestedAt = None;
1367
+ $LastLateResult = None;
1368
+ $RunningRequestId = Lookup[args, "requestId", $CurrentRequestId];
1369
+ $RunningCellId = cellId;
1370
+ $RunningNotebookId = notebookId;
1371
+ $RunningNotebookObject = notebook;
1372
+ $RunningStartedAt = AbsoluteTime[];
1373
+ If[!NumericQ[timeoutSec], timeoutSec = 120];
1374
+ $RunningTimeoutAt = AbsoluteTime[] + timeoutSec;
1375
+ SelectionMove[cell, All, Cell];
1376
+ (* RunCell returns immediately; Palette-local cancellation can still abort the running evaluation. *)
1377
+ evaluateResult = Quiet @ Check[FrontEndTokenExecute[notebook, "EvaluateCells"], $Failed];
1378
+ If[evaluateResult === $Failed,
1379
+ ClearRunningEvaluationState[];
1380
+ Return[BridgeFailure["RUN_FAILED", "The FrontEnd failed to start evaluating the cell."]]
1381
+ ];
1382
+ $BridgeNotebooks[notebookId] = Join[record, <|"closed" -> False|>];
1383
+ <|"status" -> "started", "cellId" -> cellId|>
1384
+ ];
1385
+
1386
+ AbortEvaluationRequest[args_Association] := Module[{notebookId, record, notebook, runningCellId, runningRequestId, wasRunning},
1387
+ If[Not @ ConfirmAction["RunCell", "AI requests aborting the running evaluation. Allow?"], Return[$Canceled]];
1388
+ notebookId = TargetNotebookId[args];
1389
+ If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1390
+ record = NotebookRecord[notebookId];
1391
+ If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1392
+ notebook = Lookup[record, "notebook", None];
1393
+ If[Head[notebook] =!= NotebookObject, Return[Failure["BAD_REQUEST", <|"message" -> "Notebook is unavailable."|>]]];
1394
+ runningCellId = $RunningCellId;
1395
+ runningRequestId = $RunningRequestId;
1396
+ wasRunning = StringQ[$RunningCellId] && ($RunningNotebookId === notebookId || $RunningNotebookObject === notebook);
1397
+ If[wasRunning && StringQ[runningCellId] && CellEvaluationCompleteQ[notebook, runningCellId],
1398
+ FinishRunningCell["finished"];
1399
+ Return[<|"status" -> "finished", "cellId" -> runningCellId, "requestId" -> runningRequestId|>]
1400
+ ];
1401
+ If[wasRunning,
1402
+ $AbortRequestedAt = AbsoluteTime[];
1403
+ $RunningStatus = "abort_requested"
1404
+ ];
1405
+ Quiet @ Check[FrontEndTokenExecute[notebook, "EvaluatorAbort"], Null];
1406
+ If[wasRunning,
1407
+ <|"status" -> "abort_requested", "cellId" -> runningCellId, "requestId" -> runningRequestId|>,
1408
+ <|"status" -> "idle"|>
1409
+ ]
1410
+ ];
1411
+
1412
+ SaveNotebookRequest[args_Association] := Module[{notebookId, record, notebook},
1413
+ If[Not @ ConfirmAction["SaveNotebook", "AI requests saving the notebook. Allow?"], Return[$Canceled]];
1414
+ notebookId = TargetNotebookId[args];
1415
+ If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1416
+ record = NotebookRecord[notebookId];
1417
+ If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
1418
+ notebook = Lookup[record, "notebook", None];
1419
+ If[Head[notebook] =!= NotebookObject, Return[Failure["BAD_REQUEST", <|"message" -> "Notebook is unavailable."|>]]];
1420
+ NotebookSave[notebook];
1421
+ <|"status" -> "saved"|>
1422
+ ];
1423
+
1424
+ SelectNotebookRequest[args_Association] := Module[{notebookId, record, info, response},
1425
+ notebookId = Lookup[args, "notebookId", None];
1426
+ If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "notebookId is required."|>]]];
1427
+ record = NotebookRecord[notebookId];
1428
+ If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "Requested notebook was not found."|>]]];
1429
+ info = Lookup[record, "info", <||>];
1430
+ $ActiveNotebookId = notebookId;
1431
+ $AttachedNotebook = Lookup[record, "notebook", None];
1432
+ $AttachedNotebookInfo = info;
1433
+ $CellMap = Lookup[record, "cellMap", <||>];
1434
+ $NextCellId = Lookup[record, "nextCellId", 1];
1435
+ response = Quiet @ Check[BridgePost["/notebooks/select", <|"notebookId" -> notebookId|>], Null];
1436
+ If[response === Null, <|"status" -> "selected", "notebookId" -> notebookId|>, <|"status" -> "selected", "notebookId" -> notebookId, "bridge" -> response|>]
1437
+ ];
1438
+
1439
+ PollCancellations[] := Module[{payload, requests},
1440
+ payload = BridgeGet["/cancellations"];
1441
+ If[payload === $Failed, Return[$Failed]];
1442
+ requests = Lookup[payload, "cancelRequests", {}];
1443
+ If[
1444
+ ListQ[requests] && (MemberQ[Lookup[#, "requestId", None] & /@ requests, $CurrentRequestId] || MemberQ[Lookup[#, "requestId", None] & /@ requests, $RunningRequestId]),
1445
+ CancelCurrentRequest[]
1446
+ ];
1447
+ payload
1448
+ ];
1449
+
1450
+ RegisterAgent[] := BridgePost[
1451
+ "/agents/register",
1452
+ <|
1453
+ "agentSessionId" -> $AgentSessionId,
1454
+ "wolframVersion" -> ToString[$VersionNumber],
1455
+ "platform" -> $OperatingSystem,
1456
+ "seenAt" -> UnixTimeMilliseconds[],
1457
+ "machineId" -> ToString[$MachineID]
1458
+ |>
1459
+ ];
1460
+
1461
+ AgentHeartbeat[] := BridgePost[
1462
+ "/agents/heartbeat",
1463
+ <|
1464
+ "agentSessionId" -> $AgentSessionId,
1465
+ "seenAt" -> UnixTimeMilliseconds[]
1466
+ |>
1467
+ ];
1468
+
1469
+ NotebookHeartbeatPayload[nb_NotebookObject] := Module[{savedPath, windowTitle, displayName, frontendObjectKey},
1470
+ savedPath = Quiet @ Check[ToString[Replace[NotebookFileName[nb], $Failed -> ""]], ""];
1471
+ frontendObjectKey = FrontendObjectKey[nb];
1472
+ windowTitle = NotebookWindowTitle[nb];
1473
+ displayName = NotebookDisplayNameForHeartbeat[nb, savedPath, frontendObjectKey];
1474
+ <|
1475
+ "agentSessionId" -> $AgentSessionId,
1476
+ "frontendObjectKey" -> frontendObjectKey,
1477
+ "displayName" -> displayName,
1478
+ "windowTitle" -> windowTitle,
1479
+ "notebookPath" -> savedPath,
1480
+ "savedPath" -> savedPath,
1481
+ "wolframVersion" -> ToString[$VersionNumber],
1482
+ "platform" -> $OperatingSystem,
1483
+ "permissions" -> $BridgePermissions,
1484
+ "seenAt" -> UnixTimeMilliseconds[]
1485
+ |>
1486
+ ];
1487
+
1488
+ AgentNotebookCandidateQ[nb_NotebookObject] := Module[{frame, visible},
1489
+ frame = Quiet @ Check[CurrentValue[nb, WindowFrame], "Normal"];
1490
+ visible = Quiet @ Check[CurrentValue[nb, Visible], True];
1491
+ visible =!= False && !MemberQ[{"Palette", "ModalDialog", "ModelessDialog"}, frame]
1492
+ ];
1493
+
1494
+ AgentVisibleNotebooks[] := Module[{candidates},
1495
+ candidates = DeleteDuplicates @ Cases[
1496
+ Quiet @ Check[Notebooks[], {}],
1497
+ _NotebookObject
1498
+ ];
1499
+ Select[candidates, AgentNotebookCandidateQ]
1500
+ ];
1501
+
1502
+ AgentPostResult[requestId_String, result_Association] := BridgePost[
1503
+ "/requests/" <> URLComponentEncodeString[requestId] <> "/result",
1504
+ <|"requestId" -> requestId, "ok" -> True, "result" -> result|>
1505
+ ];
1506
+
1507
+ AgentPostFailure[requestId_String, code_String, message_String] := BridgePost[
1508
+ "/requests/" <> URLComponentEncodeString[requestId] <> "/result",
1509
+ <|"requestId" -> requestId, "ok" -> False, "error" -> <|"code" -> code, "message" -> message|> |>
1510
+ ];
1511
+
1512
+ AgentHeartbeatNotebookClosure[notebookId_String] := Module[{record = Lookup[$BridgeNotebooks, notebookId, <||>], response},
1513
+ If[!AssociationQ[record] || TrueQ[Lookup[record, "closed", False]] || Head[Lookup[record, "notebook", None]] =!= NotebookObject || !StringQ[Lookup[record, "frontendObjectKey", None]], Return[$Failed]];
1514
+ response = Quiet @ Check[BridgePost["/notebooks/" <> URLComponentEncodeString[notebookId] <> "/closed", <|"agentSessionId" -> $AgentSessionId|>], $Failed];
1515
+ If[AssociationQ[record] && AssociationQ[response] && TrueQ[Lookup[response, "ok", False]],
1516
+ If[!TrueQ[Lookup[record, "closed", False]],
1517
+ $BridgeNotebooks[notebookId] = Join[record, <|"closed" -> True|>]
1518
+ ]
1519
+ ];
1520
+ response
1521
+ ];
1522
+
1523
+ HeartbeatNotebooks[] := Module[{notebooks = AgentVisibleNotebooks[], visibleNotebookIds = {}, trackedNotebookIds, staleNotebookIds},
1524
+ trackedNotebookIds = Select[
1525
+ Keys[$BridgeNotebooks],
1526
+ Function[notebookId, Module[{record = Lookup[$BridgeNotebooks, notebookId, <||>]},
1527
+ AssociationQ[record] && !TrueQ[Lookup[record, "closed", False]] && Lookup[record, "agentSessionId", $AgentSessionId] === $AgentSessionId && Head[Lookup[record, "notebook", None]] === NotebookObject && StringQ[Lookup[record, "frontendObjectKey", None]]
1528
+ ]]
1529
+ ];
1530
+ Scan[
1531
+ Function[nb,
1532
+ Module[{payload, response, notebookId, existing, frontendObjectKey, info, cellMap, nextCellId, localNotebookId},
1533
+ localNotebookId = SelectFirst[
1534
+ Keys[$BridgeNotebooks],
1535
+ Function[candidateId, Module[{record = Lookup[$BridgeNotebooks, candidateId, <||>]},
1536
+ AssociationQ[record] && !TrueQ[Lookup[record, "closed", False]] && Lookup[record, "agentSessionId", $AgentSessionId] === $AgentSessionId && Lookup[record, "notebook", None] === nb && Head[Lookup[record, "notebook", None]] === NotebookObject && StringQ[Lookup[record, "frontendObjectKey", None]]
1537
+ ]],
1538
+ None
1539
+ ];
1540
+ If[StringQ[localNotebookId] && StringLength[localNotebookId] > 0, AppendTo[visibleNotebookIds, localNotebookId]];
1541
+ payload = NotebookHeartbeatPayload[nb];
1542
+ response = Quiet @ Check[BridgePost["/notebooks/heartbeat", payload], $Failed];
1543
+ If[AssociationQ[response],
1544
+ notebookId = Lookup[Lookup[response, "notebook", <||>], "notebookId", Lookup[response, "notebookId", None]];
1545
+ frontendObjectKey = Lookup[response, "frontendObjectKey", Lookup[payload, "frontendObjectKey", FrontendObjectKey[nb]]];
1546
+ info = Lookup[response, "info", Lookup[response, "notebookInfo", Lookup[response, "notebook", <||>]]];
1547
+ existing = Lookup[$BridgeNotebooks, notebookId, <||>];
1548
+ cellMap = Lookup[Lookup[response, "notebook", <||>], "cellMap", Lookup[response, "cellMap", Lookup[existing, "cellMap", <||>]]];
1549
+ nextCellId = Lookup[Lookup[response, "notebook", <||>], "nextCellId", Lookup[response, "nextCellId", Lookup[existing, "nextCellId", 1]]];
1550
+ If[StringQ[notebookId] && StringLength[notebookId] > 0,
1551
+ $BridgeNotebooks[notebookId] = Join[
1552
+ existing,
1553
+ <|
1554
+ "notebook" -> nb,
1555
+ "frontendObjectKey" -> frontendObjectKey,
1556
+ "agentSessionId" -> $AgentSessionId,
1557
+ "info" -> info,
1558
+ "cellMap" -> cellMap,
1559
+ "nextCellId" -> nextCellId,
1560
+ "closed" -> False
1561
+ |>
1562
+ ];
1563
+ If[!MemberQ[visibleNotebookIds, notebookId], AppendTo[visibleNotebookIds, notebookId]]
1564
+ ]
1565
+ ]
1566
+ ]
1567
+ ],
1568
+ notebooks
1569
+ ];
1570
+ staleNotebookIds = Complement[trackedNotebookIds, visibleNotebookIds];
1571
+ Scan[AgentHeartbeatNotebookClosure, staleNotebookIds];
1572
+ ];
1573
+
1574
+ PollAgentRequest[] := BridgeGet["/agents/" <> URLComponentEncodeString[$AgentSessionId] <> "/next-request"];
1575
+
1576
+ EnsureAgentRegisteredForVisibleNotebooks[] := Module[{notebooks, heartbeat, reason},
1577
+ notebooks = AgentVisibleNotebooks[];
1578
+ If[Length[notebooks] == 0, Return[notebooks]];
1579
+ heartbeat = Quiet @ Check[AgentHeartbeat[], $Failed];
1580
+ reason = If[AssociationQ[heartbeat], Lookup[Lookup[heartbeat, "error", <||>], "reason", None], None];
1581
+ If[reason === "superseded", Return[notebooks]];
1582
+ If[!AssociationQ[heartbeat] || AssociationQ[Lookup[heartbeat, "error", None]],
1583
+ RegisterAgent[]
1584
+ ];
1585
+ notebooks
1586
+ ];
1587
+
1588
+ ExecuteAgentRequest[request_Association] := Module[{args = Lookup[request, "arguments", <||>], targetNotebookId, normalizedRequest},
1589
+ targetNotebookId = Lookup[request, "targetNotebookId", None];
1590
+ If[!AssociationQ[args], args = <||>];
1591
+ If[StringQ[targetNotebookId] && StringLength[targetNotebookId] > 0, AssociateTo[args, "notebookId" -> targetNotebookId]];
1592
+ normalizedRequest = Join[request, <|"arguments" -> args|>];
1593
+ Internal`WithLocalSettings[
1594
+ $AgentExecutionInProgress = True,
1595
+ ExecuteRequest[normalizedRequest],
1596
+ $AgentExecutionInProgress = False
1597
+ ]
1598
+ ];
1599
+
1600
+ SafeHiddenAgentTick[] := Module[{payload, request},
1601
+ If[TrueQ[$HiddenAgentInProgress], Return[$LastResultStatus]];
1602
+ Internal`WithLocalSettings[
1603
+ $HiddenAgentInProgress = True,
1604
+ (
1605
+ Block[{$BridgeHTTPTimeoutSeconds = 1, $BridgeHTTPRetryCount = 1},
1606
+ Quiet @ Check[EnsureAgentRegisteredForVisibleNotebooks[], Null];
1607
+ Quiet @ Check[HeartbeatNotebooks[], Null];
1608
+ payload = Quiet @ Check[PollAgentRequest[], $Failed];
1609
+ ];
1610
+ If[AssociationQ[payload],
1611
+ If[ListQ[Lookup[payload, "cancelRequests", {}]],
1612
+ Scan[
1613
+ Function[cancelRequest,
1614
+ If[StringQ[Lookup[cancelRequest, "requestId", None]] && (Lookup[cancelRequest, "requestId", None] === $CurrentRequestId || Lookup[cancelRequest, "requestId", None] === $RunningRequestId),
1615
+ CancelCurrentRequest[]
1616
+ ]
1617
+ ],
1618
+ Lookup[payload, "cancelRequests", {}]
1619
+ ]
1620
+ ];
1621
+ request = Lookup[payload, "request", None];
1622
+ If[AssociationQ[request], ExecuteAgentRequest[request]]
1623
+ ];
1624
+ Null
1625
+ ),
1626
+ $HiddenAgentInProgress = False
1627
+ ]
1628
+ ];
1629
+
1630
+ ControlNotebookOpenQ[nb_] := Head[nb] === NotebookObject && MemberQ[Quiet @ Check[Notebooks[], {}], nb];
1631
+
1632
+ ControlAgentFlagPath[key_String] := {TaggingRules, "MMAAgentBridge", key};
1633
+
1634
+ SetControlAgentFlag[key_String, value_] := Quiet @ Check[CurrentValue[$FrontEndSession, ControlAgentFlagPath[key]] = value, Null];
1635
+
1636
+ ClearControlAgentFlag[key_String] := SetControlAgentFlag[key, Inherited];
1637
+
1638
+ StopMMAAgentHiddenAgent[] := Module[{task = $HiddenAgentTask},
1639
+ If[task =!= None,
1640
+ Quiet @ Check[RemoveScheduledTask[task], Null]
1641
+ ];
1642
+ $HiddenAgentTask = None;
1643
+ $HiddenAgentInProgress = False;
1644
+ <|"status" -> "stopped"|>
1645
+ ];
1646
+
1647
+ StopMMAAgentControlKernel[] := Module[{nb = $ControlAgentNotebook},
1648
+ If[ControlNotebookOpenQ[nb], Quiet @ Check[NotebookClose[nb], Null]];
1649
+ $ControlAgentNotebook = None;
1650
+ ClearControlAgentFlag["AgentRunning"];
1651
+ <|"status" -> "closed"|>
1652
+ ];
1653
+
1654
+ EnsureControlEvaluator[evaluatorName_String] := Module[{before, beforeAssoc, localSpec, controlSpec, afterAssoc},
1655
+ before = Quiet @ Check[CurrentValue[$FrontEnd, EvaluatorNames], $Failed];
1656
+ If[before === $Failed, Return[BridgeFailure["EVALUATOR_CONFIG_FAILED", "Could not read FrontEnd evaluator configuration."]]];
1657
+ beforeAssoc = Association[before];
1658
+ If[KeyExistsQ[beforeAssoc, evaluatorName],
1659
+ Return[<|"status" -> "exists", "evaluatorName" -> evaluatorName, "spec" -> Lookup[beforeAssoc, evaluatorName]|>]
1660
+ ];
1661
+ localSpec = Lookup[beforeAssoc, "Local", {"AutoStartOnLaunch" -> False}];
1662
+ controlSpec = Append[DeleteCases[localSpec, HoldPattern["AutoStartOnLaunch" -> _]], "AutoStartOnLaunch" -> False];
1663
+ Quiet @ Check[
1664
+ CurrentValue[$FrontEnd, {EvaluatorNames, evaluatorName}] = controlSpec,
1665
+ Return[BridgeFailure["EVALUATOR_CONFIG_FAILED", "Failed to configure the control evaluator."]]
1666
+ ];
1667
+ afterAssoc = Association[CurrentValue[$FrontEnd, EvaluatorNames]];
1668
+ If[!KeyExistsQ[afterAssoc, evaluatorName],
1669
+ Return[BridgeFailure["EVALUATOR_CONFIG_FAILED", "The control evaluator was not present after configuration."]]
1670
+ ];
1671
+ <|"status" -> "created", "evaluatorName" -> evaluatorName, "spec" -> Lookup[afterAssoc, evaluatorName]|>
1672
+ ];
1673
+
1674
+ ControlAgentInitCode[permissions_Association] := Module[{source = $MMAAgentBridgeSourceFile},
1675
+ StringJoin[
1676
+ "Quiet @ Check[CurrentValue[$FrontEndSession, {TaggingRules, \"MMAAgentBridge\", \"ControlKernelBooting\"}] = Inherited, Null];\n",
1677
+ "If[StringQ[", ToString[source, InputForm], "] && FileExistsQ[", ToString[source, InputForm], "], Get[ToString[", ToString[source, InputForm], "]], Needs[\"MMAAgentBridge`\"]];\n",
1678
+ "MMAAgentBridge`Private`$BridgePermissions = ", ToString[permissions, InputForm], ";\n",
1679
+ "MMAAgentBridge`StartMMAAgentHiddenAgent[]"
1680
+ ]
1681
+ ];
1682
+
1683
+ StartMMAAgentControlKernel[evaluatorName_String:$ControlAgentEvaluatorName, stopCurrentAgent_:True] := Module[{evaluatorStatus, permissions, initCode},
1684
+ If[ControlNotebookOpenQ[$ControlAgentNotebook],
1685
+ Return[<|"status" -> "already_running", "evaluatorName" -> evaluatorName|>]
1686
+ ];
1687
+ SetControlAgentFlag["ControlKernelBooting", True];
1688
+ evaluatorStatus = EnsureControlEvaluator[evaluatorName];
1689
+ If[MatchQ[evaluatorStatus, _Failure],
1690
+ ClearControlAgentFlag["ControlKernelBooting"];
1691
+ Return[evaluatorStatus]
1692
+ ];
1693
+ permissions = $BridgePermissions;
1694
+ initCode = ControlAgentInitCode[permissions];
1695
+ If[TrueQ[stopCurrentAgent], StopMMAAgentHiddenAgent[]];
1696
+ $ControlAgentEvaluatorName = evaluatorName;
1697
+ $ControlAgentNotebook = CreateDocument[{Cell[BoxData[initCode], "Input", CellTags -> {"MMAAgentControlInit"}]}, Visible -> False, WindowTitle -> "MMA Agent Control Kernel", Saveable -> False, Evaluator -> evaluatorName];
1698
+ SelectionMove[$ControlAgentNotebook, All, Notebook];
1699
+ Quiet @ Check[FrontEndTokenExecute[$ControlAgentNotebook, "EvaluateCells"],
1700
+ Quiet @ Check[NotebookClose[$ControlAgentNotebook], Null];
1701
+ $ControlAgentNotebook = None;
1702
+ ClearControlAgentFlag["ControlKernelBooting"];
1703
+ Return[BridgeFailure["CONTROL_AGENT_START_FAILED", "Failed to evaluate the control agent initialization cell."]]
1704
+ ];
1705
+ SetControlAgentFlag["AgentRunning", True];
1706
+ <|"status" -> "started", "evaluatorName" -> evaluatorName, "evaluator" -> evaluatorStatus|>
1707
+ ];
1708
+
1709
+ StartMMAAgentHiddenAgent[] := Module[{},
1710
+ Block[{$BridgeHTTPTimeoutSeconds = 1, $BridgeHTTPRetryCount = 1},
1711
+ EnsureAgentRegisteredForVisibleNotebooks[];
1712
+ HeartbeatNotebooks[];
1713
+ ];
1714
+ If[$HiddenAgentTask === None || Not @ TrueQ @ Quiet @ Check[ScheduledTaskActiveQ[$HiddenAgentTask], False],
1715
+ $HiddenAgentTask = RunScheduledTask[SafeHiddenAgentTick[], 1]
1716
+ ];
1717
+ $HiddenAgentTask
1718
+ ];
1719
+
1720
+ SymbolDetail[sym_Symbol] := Module[{name = SymbolName[sym]},
1721
+ <|
1722
+ "status" -> "found",
1723
+ "symbol" -> name,
1724
+ "usage" -> Quiet @ ToString[sym::usage],
1725
+ "options" -> Quiet @ Map[<|"name" -> ToString[#[[1]]], "default" -> ToString[#[[2]]]|> &, Options[sym]],
1726
+ "attributes" -> ToString /@ Attributes[sym],
1727
+ "url" -> "https://reference.wolfram.com/language/ref/" <> name <> ".html"
1728
+ |>
1729
+ ];
1730
+
1731
+ SymbolCandidate[sym_String] := Module[{s = ToExpression[sym, StandardForm, Hold]},
1732
+ <|"symbol" -> sym,
1733
+ "usage" -> StringTake[Quiet @ ToString[ReleaseHold[s]::usage], UpTo[200]]
1734
+ |>
1735
+ ];
1736
+
1737
+ SymbolLookup[query_String] := Module[{sym, candidates, exactName},
1738
+ If[StringLength[StringTrim[query]] == 0,
1739
+ Return[<|"status" -> "bad_request", "message" -> "Query must not be empty."|>]
1740
+ ];
1741
+ exactName = "System`" <> query;
1742
+ If[Length[Names[exactName]] === 1,
1743
+ sym = ToExpression[exactName];
1744
+ Return @ SymbolDetail[sym]
1745
+ ];
1746
+
1747
+ candidates = Names["System`*" <> query <> "*"];
1748
+ If[candidates === {},
1749
+ Return[<|"status" -> "not_found", "query" -> query,
1750
+ "message" -> "No System` symbols match '" <> query <> "'"|>]
1751
+ ];
1752
+
1753
+ <|"status" -> "ambiguous", "query" -> query,
1754
+ "candidates" -> Map[SymbolCandidate, Take[candidates, UpTo[20]]]
1755
+ |>
1756
+ ];
1757
+
1758
+ ExecuteRequest[request_Association] := Module[{requestId, tool, args, result},
1759
+ requestId = Lookup[request, "requestId", None];
1760
+ If[!StringQ[requestId] || StringLength[requestId] == 0, Return[PostFailure["unknown", "BAD_REQUEST", "Request missing valid requestId."]]];
1761
+ tool = Lookup[request, "tool", None];
1762
+ args = Lookup[request, "arguments", <||>];
1763
+ Internal`WithLocalSettings[
1764
+ $CurrentRequestId = requestId,
1765
+ result = Quiet @ Check[
1766
+ Which[
1767
+ !StringQ[tool], Failure["BAD_REQUEST", <|"Message" -> "Request missing valid tool."|>],
1768
+ !AssociationQ[args], Failure["BAD_REQUEST", <|"Message" -> "Request arguments must be an association."|>],
1769
+ True,
1770
+ Switch[tool,
1771
+ "mma_list_cells", If[RequireReadPermission[] === $Canceled, $Canceled, Module[{notebookId = TargetNotebookId[args], refresh}, If[!StringQ[notebookId] || StringLength[notebookId] == 0, Failure["BAD_REQUEST", <|"Message" -> "No notebook is selected."|>], refresh = RefreshCellMap[notebookId]; If[MatchQ[refresh, _Failure], refresh, <|"cells" -> refresh|>]]]],
1772
+ "mma_read_cell", ReadCellById[args],
1773
+ "mma_insert_cell", InsertCellRequest[args],
1774
+ "mma_modify_cell", ModifyCellRequest[args],
1775
+ "mma_delete_cell", DeleteCellRequest[args],
1776
+ "mma_run_cell", RunCellRequest[args],
1777
+ "mma_abort_evaluation", AbortEvaluationRequest[args],
1778
+ "mma_get_cell_output", GetCellOutputById[args],
1779
+ "mma_read_artifact", ReadArtifactById[args],
1780
+ "mma_save_notebook", SaveNotebookRequest[args],
1781
+ "mma_select_notebook", SelectNotebookRequest[args],
1782
+ "mma_symbol_lookup", SymbolLookup[Lookup[args, "query", ""]],
1783
+ _, Failure["BAD_REQUEST", <|"Message" -> StringTemplate["Unknown tool ``."][tool]|>]
1784
+ ]
1785
+ ],
1786
+ $Failed
1787
+ ];
1788
+ Which[
1789
+ result === $Canceled,
1790
+ PostFailure[requestId, "USER_DENIED", "User denied or cancelled the operation."],
1791
+ MatchQ[result, Failure[_, _Association]],
1792
+ PostFailure[requestId, FailedRequestCode[result], FailedRequestMessage[result]],
1793
+ result === $Failed,
1794
+ PostFailure[requestId, "WOLFRAM_ERROR", "The Wolfram bridge failed to execute the request."],
1795
+ True,
1796
+ PostResult[requestId, result]
1797
+ ],
1798
+ $CurrentRequestId = None
1799
+ ]
1800
+ ];
1801
+
1802
+ PollBridge[] := Module[{payload, request, status, cancelRequests, requestIds, queueResult},
1803
+ payload = BridgeGet["/poll?paletteId=" <> URLComponentEncodeString[$PaletteId] <> "&activeNotebookId=" <> URLComponentEncodeString[$ActiveNotebookId]];
1804
+ $LastPollTime = AbsoluteTime[];
1805
+ If[payload === $Failed,
1806
+ $LastError = "Bridge unavailable";
1807
+ Return[$Failed]
1808
+ ];
1809
+ $LastError = None;
1810
+ CheckRunningTimeout[];
1811
+ If[AssociationQ[payload],
1812
+ status = Lookup[payload, "status", None];
1813
+ If[AssociationQ[status], $LastStatus = status];
1814
+ cancelRequests = Lookup[payload, "cancelRequests", {}];
1815
+ If[ListQ[cancelRequests],
1816
+ requestIds = Lookup[#, "requestId", None] & /@ cancelRequests;
1817
+ If[MemberQ[requestIds, $CurrentRequestId] || MemberQ[requestIds, $RunningRequestId], CancelCurrentRequest[]]
1818
+ ];
1819
+ request = Lookup[payload, "request", None];
1820
+ If[AssociationQ[request],
1821
+ queueResult = EnqueueBridgeRequest[request];
1822
+ AssociateTo[payload, "status" -> "queued", "queueResult" -> queueResult],
1823
+ payload
1824
+ ],
1825
+ payload
1826
+ ]
1827
+ ];
1828
+
1829
+ End[];
1830
+
1831
+ EndPackage[];