@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.
- package/CHANGELOG.md +14 -0
- package/CONTRIBUTING.md +22 -0
- package/LICENSE +21 -0
- package/README.md +308 -0
- package/README.zh-CN.md +308 -0
- package/SECURITY.md +22 -0
- package/dist/src/backend/agentRegistry.js +115 -0
- package/dist/src/backend/backendQueue.js +212 -0
- package/dist/src/backend/backendState.js +99 -0
- package/dist/src/backend/notebookRegistry.js +136 -0
- package/dist/src/backend/protocol.js +32 -0
- package/dist/src/bridge/httpBridge.js +366 -0
- package/dist/src/bridge/requestQueue.js +200 -0
- package/dist/src/bun/dashboard.js +387 -0
- package/dist/src/bun/httpServer.js +356 -0
- package/dist/src/bun/index.js +91 -0
- package/dist/src/cli/doctor.js +235 -0
- package/dist/src/cli/index.js +125 -0
- package/dist/src/index.js +54 -0
- package/dist/src/mcp/backendTools.js +216 -0
- package/dist/src/mcp/descriptions.js +6 -0
- package/dist/src/mcp/prompts.js +52 -0
- package/dist/src/mcp/toolResults.js +183 -0
- package/dist/src/mcp/toolSchemas.js +60 -0
- package/dist/src/mcp/tools.js +161 -0
- package/dist/src/runtime/config.js +76 -0
- package/dist/src/runtime/session.js +14 -0
- package/dist/src/runtimeOptions.js +3 -0
- package/dist/src/types.js +2 -0
- package/package.json +63 -0
- package/paclet/FrontEnd/Palettes/MMAAgentBridge.nb +22 -0
- package/paclet/Kernel/MMAAgentBridge.wl +1831 -0
- package/paclet/Kernel/init.wl +1 -0
- package/paclet/PacletInfo.wl +14 -0
- package/scripts/install.js +526 -0
- package/src/bun/index.ts +120 -0
|
@@ -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[];
|