@aliceshimada/mica 1.1.0 → 1.2.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 +13 -0
- package/dist/src/bun/httpServer.js +2 -2
- package/dist/src/bun/index.js +1 -1
- package/dist/src/cli/index.js +13 -8
- package/dist/src/mcp/backendTools.js +33 -1
- package/dist/src/mcp/prompts.js +3 -1
- package/dist/src/mcp/toolSchemas.js +6 -0
- package/package.json +1 -1
- package/paclet/Kernel/MMAAgentBridge.wl +437 -363
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.0 - 2026-06-16
|
|
4
|
+
|
|
5
|
+
- Add `mma_create_notebook` tool: create a new blank notebook in the Wolfram FrontEnd.
|
|
6
|
+
- Add `mma_open_notebook` tool: open an existing notebook file (.nb) from disk.
|
|
7
|
+
- Add `CreateNotebook` and `OpenNotebook` permissions (default false).
|
|
8
|
+
- Fix agent-level tools when no live notebook exists for routing.
|
|
9
|
+
|
|
10
|
+
## 1.1.1 - 2026-06-16
|
|
11
|
+
|
|
12
|
+
- Fix `RestartKernelRequest`: kill kernel via `Quit[]` before restarting.
|
|
13
|
+
- Fix `realpathSync` crash when `process.argv[1]` doesn't resolve.
|
|
14
|
+
- Fix `$BridgeNotebookPermissions` memory leak: prune on notebook close.
|
|
15
|
+
|
|
3
16
|
## 1.1.0 - 2026-06-09
|
|
4
17
|
|
|
5
18
|
- Add `mma_kill_kernel` tool: quit a notebook's Wolfram kernel (control agent kernel is protected).
|
|
@@ -3,7 +3,7 @@ import http from "node:http";
|
|
|
3
3
|
import { executeBackendMcpTool } from "../mcp/backendTools.js";
|
|
4
4
|
import { renderDashboard } from "./dashboard.js";
|
|
5
5
|
const JSON_BODY_LIMIT_BYTES = 1024 * 1024;
|
|
6
|
-
const DEFAULT_VERSION = "1.
|
|
6
|
+
const DEFAULT_VERSION = "1.2.0";
|
|
7
7
|
export async function createBunHttpApp({ state, host = "127.0.0.1", port, authToken, version = DEFAULT_VERSION }) {
|
|
8
8
|
const runtimeInfo = {
|
|
9
9
|
host,
|
|
@@ -338,7 +338,7 @@ function readPermissions(value) {
|
|
|
338
338
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
339
339
|
throw new Error("BAD_REQUEST");
|
|
340
340
|
const record = value;
|
|
341
|
-
const keys = ["ReadNotebook", "InsertCell", "ModifyCell", "DeleteCell", "RunCell", "SaveNotebook"];
|
|
341
|
+
const keys = ["ReadNotebook", "InsertCell", "ModifyCell", "DeleteCell", "RunCell", "SaveNotebook", "CreateNotebook", "OpenNotebook"];
|
|
342
342
|
const permissions = {};
|
|
343
343
|
for (const key of keys) {
|
|
344
344
|
if (typeof record[key] !== "boolean")
|
package/dist/src/bun/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { loadRuntimeConfig } from "../runtime/config.js";
|
|
|
8
8
|
import { writeSessionFile } from "../runtime/session.js";
|
|
9
9
|
import { createBunHttpApp } from "./httpServer.js";
|
|
10
10
|
const MCP_SERVER_NAME = "mica-bun";
|
|
11
|
-
const MICA_PACKAGE_VERSION = "1.
|
|
11
|
+
const MICA_PACKAGE_VERSION = "1.2.0";
|
|
12
12
|
export async function startBunRuntime(deps = {}) {
|
|
13
13
|
const config = deps.runtimeConfig ?? loadRuntimeConfig();
|
|
14
14
|
const bridgeOnly = deps.bridgeOnly ?? config.bridgeOnly;
|
package/dist/src/cli/index.js
CHANGED
|
@@ -223,13 +223,18 @@ async function main() {
|
|
|
223
223
|
process.exitCode = exitCode;
|
|
224
224
|
}
|
|
225
225
|
if (process.argv[1]) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
226
|
+
try {
|
|
227
|
+
const scriptReal = realpathSync(fileURLToPath(import.meta.url));
|
|
228
|
+
const argReal = realpathSync(process.argv[1]);
|
|
229
|
+
if (scriptReal === argReal) {
|
|
230
|
+
main().catch((error) => {
|
|
231
|
+
const message = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
232
|
+
process.stderr.write(`${message}\n`);
|
|
233
|
+
process.exitCode = 1;
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
// process.argv[1] may not resolve (e.g. node -e, shebang edge cases)
|
|
234
239
|
}
|
|
235
240
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { DEFAULT_TIMEOUTS_MS } from "../backend/protocol.js";
|
|
3
|
-
import { abortEvaluationSchema, deleteCellSchema, getCellOutputSchema, insertCellSchema, killKernelSchema, listCellsSchema, modifyCellSchema, noArgsSchema, readArtifactSchema, readCellSchema, restartKernelSchema, runCellSchema, selectNotebookSchema, saveNotebookSchema, symbolLookupSchema, } from "./toolSchemas.js";
|
|
3
|
+
import { abortEvaluationSchema, createNotebookSchema, deleteCellSchema, getCellOutputSchema, insertCellSchema, killKernelSchema, listCellsSchema, modifyCellSchema, noArgsSchema, openNotebookSchema, readArtifactSchema, readCellSchema, restartKernelSchema, runCellSchema, selectNotebookSchema, saveNotebookSchema, symbolLookupSchema, } from "./toolSchemas.js";
|
|
4
4
|
import { INSERT_ANCHOR_GUIDANCE, notebookToolDescription } from "./descriptions.js";
|
|
5
5
|
import { toolFailure, toolSuccess, withToolErrors } from "./toolResults.js";
|
|
6
6
|
function assertLiveAgent(state) {
|
|
@@ -196,6 +196,16 @@ export const MICA_BACKEND_TOOL_DEFINITIONS = [
|
|
|
196
196
|
description: notebookToolDescription("Select the active Mathematica notebook in the backend registry."),
|
|
197
197
|
schema: selectNotebookSchema.shape,
|
|
198
198
|
},
|
|
199
|
+
{
|
|
200
|
+
name: "mma_create_notebook",
|
|
201
|
+
description: notebookToolDescription("Create a new blank notebook in the Wolfram FrontEnd with the given window title."),
|
|
202
|
+
schema: createNotebookSchema.shape,
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: "mma_open_notebook",
|
|
206
|
+
description: notebookToolDescription("Open an existing notebook file (.nb) from disk in the Wolfram FrontEnd."),
|
|
207
|
+
schema: openNotebookSchema.shape,
|
|
208
|
+
},
|
|
199
209
|
...queuedNotebookTools.map((config) => ({
|
|
200
210
|
name: config.name,
|
|
201
211
|
description: notebookToolDescription(config.summary, config.extraGuidance),
|
|
@@ -231,6 +241,28 @@ export async function executeBackendMcpTool(state, tool, args = {}, extra) {
|
|
|
231
241
|
return toolSuccess({ activeNotebookId: state.activeNotebookId, notebook: target.notebook });
|
|
232
242
|
});
|
|
233
243
|
}
|
|
244
|
+
// Agent-level tools: route to any live agent without requiring a notebook target
|
|
245
|
+
if (tool === "mma_create_notebook" || tool === "mma_open_notebook") {
|
|
246
|
+
return withToolErrors({ tool, args: recordArgs }, () => {
|
|
247
|
+
sweepStateLiveness(state);
|
|
248
|
+
const live = state.agents.list().find((a) => !a.offline && !a.retired);
|
|
249
|
+
if (!live)
|
|
250
|
+
throw new Error("NO_LIVE_AGENT");
|
|
251
|
+
const notebook = state.notebooks.listLive().find((n) => n.agentSessionId === live.agentSessionId);
|
|
252
|
+
const targetNotebookId = notebook?.notebookId ?? `__agent__${live.agentSessionId}`;
|
|
253
|
+
const requestId = randomUUID();
|
|
254
|
+
state.queue.enqueue({
|
|
255
|
+
requestId,
|
|
256
|
+
tool,
|
|
257
|
+
arguments: recordArgs,
|
|
258
|
+
targetNotebookId,
|
|
259
|
+
agentSessionId: live.agentSessionId,
|
|
260
|
+
timeoutMs: DEFAULT_TIMEOUTS_MS.mutation,
|
|
261
|
+
createdAt: Date.now(),
|
|
262
|
+
});
|
|
263
|
+
return toolSuccess({ status: "queued", requestId });
|
|
264
|
+
});
|
|
265
|
+
}
|
|
234
266
|
const queuedConfig = queuedNotebookTools.find((config) => config.name === tool);
|
|
235
267
|
if (!queuedConfig) {
|
|
236
268
|
return toolFailure(new Error(`UNKNOWN_TOOL: ${tool}`), { tool, args: recordArgs });
|
package/dist/src/mcp/prompts.js
CHANGED
|
@@ -14,6 +14,8 @@ const TOOL_GUIDE = [
|
|
|
14
14
|
["mma_abort_evaluation", "Abort a running notebook evaluation."],
|
|
15
15
|
["mma_kill_kernel", "Quit the Wolfram kernel for a notebook (control agent kernel is protected)."],
|
|
16
16
|
["mma_restart_kernel", "Restart the Wolfram kernel for a notebook so it can evaluate cells again."],
|
|
17
|
+
["mma_create_notebook", "Create a new blank notebook in the Wolfram FrontEnd."],
|
|
18
|
+
["mma_open_notebook", "Open an existing notebook file (.nb) from disk in the Wolfram FrontEnd."],
|
|
17
19
|
["mma_get_cell_output", "Read output and messages produced by one cell; this may refresh completed run status."],
|
|
18
20
|
["mma_read_artifact", "Read large output or message artifacts by byte page; ids may become stale after notebook edits or reruns."],
|
|
19
21
|
["mma_save_notebook", "Save the selected notebook when SaveNotebook permission is granted."],
|
|
@@ -35,7 +37,7 @@ export const MICA_AGENT_INSTRUCTIONS = [
|
|
|
35
37
|
"Tools:",
|
|
36
38
|
...TOOL_GUIDE.map(([name, description]) => `- ${name}: ${description}`),
|
|
37
39
|
].join("\n");
|
|
38
|
-
export function createMicaMcpServer(name, version = "1.
|
|
40
|
+
export function createMicaMcpServer(name, version = "1.2.0") {
|
|
39
41
|
return new McpServer({ name, version }, { instructions: MICA_AGENT_INSTRUCTIONS });
|
|
40
42
|
}
|
|
41
43
|
export function registerMicaPrompts(server) {
|
|
@@ -44,6 +44,12 @@ export const killKernelSchema = z.object({
|
|
|
44
44
|
export const restartKernelSchema = z.object({
|
|
45
45
|
...notebookSelectorFields
|
|
46
46
|
}).strict();
|
|
47
|
+
export const createNotebookSchema = z.object({
|
|
48
|
+
title: z.string().min(1).describe("Window title for the new notebook")
|
|
49
|
+
}).strict();
|
|
50
|
+
export const openNotebookSchema = z.object({
|
|
51
|
+
path: z.string().min(1).describe("Absolute path to an existing .nb file")
|
|
52
|
+
}).strict();
|
|
47
53
|
export const getCellOutputSchema = z.object({
|
|
48
54
|
...notebookSelectorFields,
|
|
49
55
|
cellId: z.string().min(1),
|
package/package.json
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
(* ::Package:: *)
|
|
2
|
+
|
|
1
3
|
BeginPackage["MMAAgentBridge`"];
|
|
2
4
|
|
|
3
5
|
StartMMAAgentPalette::usage = "StartMMAAgentPalette[] opens the MMA Agent Bridge palette.";
|
|
@@ -15,10 +17,10 @@ PollCancellations::usage = "PollCancellations[] polls palette-originated cancell
|
|
|
15
17
|
|
|
16
18
|
Begin["`Private`"];
|
|
17
19
|
|
|
18
|
-
$DefaultBridgeBaseURL = "http://127.0.0.1:19791";
|
|
19
|
-
$BridgeBaseURL = $DefaultBridgeBaseURL;
|
|
20
|
-
$BridgeAuthToken = None;
|
|
21
|
-
$BridgeSessionFile = Automatic;
|
|
20
|
+
$DefaultBridgeBaseURL = "http://127.0.0.1:19791";
|
|
21
|
+
$BridgeBaseURL = $DefaultBridgeBaseURL;
|
|
22
|
+
$BridgeAuthToken = None;
|
|
23
|
+
$BridgeSessionFile = Automatic;
|
|
22
24
|
$BridgeNotebooks = <||>;
|
|
23
25
|
$ActiveNotebookId = None;
|
|
24
26
|
$PaletteId = CreateUUID["palette-"];
|
|
@@ -33,24 +35,24 @@ $RunningNotebookId = None;
|
|
|
33
35
|
$RunningNotebookObject = None;
|
|
34
36
|
$RunningCellObject = None;
|
|
35
37
|
$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;
|
|
38
|
+
$RunningCellRestoreEpilogRule = HoldComplete[CellEpilog -> Inherited];
|
|
39
|
+
$RunningStartedAt = None;
|
|
40
|
+
$RunningStatus = None;
|
|
41
|
+
$RunningStatusGraceSeconds = 2.0;
|
|
42
|
+
$RunningTimeoutAt = None;
|
|
43
|
+
$AbortRequestedAt = None;
|
|
44
|
+
(* Reserved for future late-result surfacing; backend queue already rejects late timeout/cancel results. *)
|
|
45
|
+
$LastLateResult = None;
|
|
46
|
+
$LastRunStatusCellId = None;
|
|
45
47
|
$LastRunStatusNotebookId = None;
|
|
46
48
|
$LastRunStatus = None;
|
|
47
49
|
$LastStatus = <||>;
|
|
48
|
-
$LastError = None;
|
|
49
|
-
$PollingInProgress = False;
|
|
50
|
-
$MaxArtifactScanCells = 20;
|
|
51
|
-
$DefaultMaxCellPayloadBytes = 262144;
|
|
52
|
-
$MaxCellPayloadBytes = 1024 * 1024;
|
|
53
|
-
$BridgeHTTPTimeoutSeconds = 30;
|
|
50
|
+
$LastError = None;
|
|
51
|
+
$PollingInProgress = False;
|
|
52
|
+
$MaxArtifactScanCells = 20;
|
|
53
|
+
$DefaultMaxCellPayloadBytes = 262144;
|
|
54
|
+
$MaxCellPayloadBytes = 1024 * 1024;
|
|
55
|
+
$BridgeHTTPTimeoutSeconds = 30;
|
|
54
56
|
$BridgeHTTPRetryCount = 3;
|
|
55
57
|
$BridgeHTTPRetryDelaySeconds = 0.25;
|
|
56
58
|
$BridgeInbox = {};
|
|
@@ -68,43 +70,43 @@ $MMAAgentBridgeSourceFile = If[StringQ[$InputFileName] && StringLength[$InputFil
|
|
|
68
70
|
$ControlAgentNotebook = None;
|
|
69
71
|
$ControlAgentEvaluatorName = "MMAAgentControl";
|
|
70
72
|
|
|
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
|
-
];
|
|
73
|
+
UnixTimeMilliseconds[] := Round[1000 UnixTime[]];
|
|
74
|
+
|
|
75
|
+
NonEmptyStringQ[value_] := StringQ[value] && StringLength[value] > 0;
|
|
76
|
+
|
|
77
|
+
EnvironmentValue[name_String] := Module[{value},
|
|
78
|
+
value = Quiet @ Check[Environment[name], ""];
|
|
79
|
+
If[NonEmptyStringQ[value], value, ""]
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
DefaultBridgeSessionFile[] := Module[{override, home},
|
|
83
|
+
override = EnvironmentValue["MICA_SESSION_FILE"];
|
|
84
|
+
If[NonEmptyStringQ[override], Return[override]];
|
|
85
|
+
home = EnvironmentValue["HOME"];
|
|
86
|
+
If[!NonEmptyStringQ[home], home = EnvironmentValue["USERPROFILE"]];
|
|
87
|
+
If[!NonEmptyStringQ[home], home = Directory[]];
|
|
88
|
+
FileNameJoin[{home, ".mica", "session.json"}]
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
If[$BridgeSessionFile === Automatic, $BridgeSessionFile = DefaultBridgeSessionFile[]];
|
|
92
|
+
|
|
93
|
+
LoadBridgeSession[] := Module[{sessionFile = $BridgeSessionFile, payload},
|
|
94
|
+
If[!StringQ[sessionFile] || !FileExistsQ[sessionFile], Return[<||>]];
|
|
95
|
+
payload = Quiet @ Check[Import[sessionFile, "RawJSON"], $Failed];
|
|
96
|
+
If[AssociationQ[payload], payload, <||>]
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
ConfigureBridgeFromSession[] := Module[{session, baseUrl, token},
|
|
100
|
+
session = LoadBridgeSession[];
|
|
101
|
+
baseUrl = Lookup[session, "baseUrl", None];
|
|
102
|
+
If[NonEmptyStringQ[baseUrl],
|
|
103
|
+
$BridgeBaseURL = baseUrl,
|
|
104
|
+
$BridgeBaseURL = $DefaultBridgeBaseURL
|
|
105
|
+
];
|
|
106
|
+
token = Lookup[session, "authToken", None];
|
|
107
|
+
$BridgeAuthToken = If[NonEmptyStringQ[token], token, None];
|
|
108
|
+
$BridgeBaseURL
|
|
109
|
+
];
|
|
108
110
|
|
|
109
111
|
(* $BridgePermissions replaces the plan's $Permissions name because *)
|
|
110
112
|
(* $Permissions is a protected Wolfram built-in symbol (Set::wrsym). *)
|
|
@@ -115,18 +117,40 @@ $DefaultBridgePermissions = <|
|
|
|
115
117
|
"ModifyCell" -> False,
|
|
116
118
|
"DeleteCell" -> False,
|
|
117
119
|
"RunCell" -> False,
|
|
118
|
-
"SaveNotebook" -> False
|
|
119
|
-
|
|
120
|
+
"SaveNotebook" -> False,
|
|
121
|
+
"CreateNotebook" -> False,
|
|
122
|
+
"OpenNotebook" -> False
|
|
123
|
+
|>;
|
|
120
124
|
|
|
121
125
|
If[!AssociationQ[Quiet @ Check[$BridgePermissions, None]],
|
|
122
126
|
$BridgePermissions = $DefaultBridgePermissions
|
|
123
127
|
];
|
|
124
128
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
If[!AssociationQ[Quiet @ Check[$BridgeNotebookPermissions, None]],
|
|
130
|
+
$BridgeNotebookPermissions = <||>
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
NotebookPermissions[notebookId_String] := Lookup[$BridgeNotebookPermissions, notebookId, $DefaultBridgePermissions];
|
|
134
|
+
|
|
135
|
+
SetNotebookPermissions[notebookId_String, perms_Association] := (
|
|
136
|
+
$BridgeNotebookPermissions[notebookId] = perms
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
PalettePermissionRow[label_String, key_String, notebookId_:None] := Module[{perms, setter},
|
|
140
|
+
perms = If[StringQ[notebookId] && StringLength[notebookId] > 0,
|
|
141
|
+
NotebookPermissions[notebookId],
|
|
142
|
+
$BridgePermissions
|
|
143
|
+
];
|
|
144
|
+
setter = If[StringQ[notebookId] && StringLength[notebookId] > 0,
|
|
145
|
+
Function[val, SetNotebookPermissions[notebookId, Join[perms, <|key -> val|>]]],
|
|
146
|
+
Function[val, $BridgePermissions[key] = val; Quiet @ Check[PostPermissions[], Null]]
|
|
147
|
+
];
|
|
148
|
+
Row[{
|
|
149
|
+
Checkbox[Dynamic[perms[key], (setter[#]) &]],
|
|
150
|
+
Spacer[8],
|
|
151
|
+
Style[label, 11]
|
|
152
|
+
}]
|
|
153
|
+
];
|
|
130
154
|
|
|
131
155
|
PaletteStatusSummary[] := Module[{server, paletteConnected, notebookAttached, pendingRequests, attachedNotebook, error},
|
|
132
156
|
server = Lookup[$LastStatus, "server", "unknown"];
|
|
@@ -181,7 +205,7 @@ NotebookDisplayName[record_Association] := Module[{info, title, path, notebookId
|
|
|
181
205
|
path = StringTrim[ToString[Lookup[info, "notebookPath", ""]]];
|
|
182
206
|
If[title === "" && path === "",
|
|
183
207
|
If[StringQ[notebookId] && StringLength[notebookId] > 0, notebookId, "Untitled notebook"],
|
|
184
|
-
If[path === "", title, title <> "
|
|
208
|
+
If[path === "", title, title <> " \[LongDash] " <> FileNameTake[path]]
|
|
185
209
|
]
|
|
186
210
|
];
|
|
187
211
|
|
|
@@ -291,7 +315,7 @@ RuntimeStatusCard[] := Module[{paletteConnected, transportMode, executorState, p
|
|
|
291
315
|
Style["Running request: ", Bold],
|
|
292
316
|
ToString[Lookup[runningRequest, "tool", "request"]],
|
|
293
317
|
If[StringQ[Lookup[runningRequest, "requestId", ""]], " (" <> Lookup[runningRequest, "requestId", ""] <> ")", Nothing],
|
|
294
|
-
If[NumberQ[elapsedSeconds], "
|
|
318
|
+
If[NumberQ[elapsedSeconds], " \[Bullet] " <> ToString[elapsedSeconds] <> "s", Nothing]
|
|
295
319
|
},
|
|
296
320
|
Nothing
|
|
297
321
|
]],
|
|
@@ -332,14 +356,16 @@ PermissionsPanel[] := Panel[
|
|
|
332
356
|
Column[
|
|
333
357
|
{
|
|
334
358
|
Style["Permissions", Bold],
|
|
335
|
-
Grid[
|
|
359
|
+
Dynamic @ Grid[
|
|
336
360
|
{
|
|
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"]}
|
|
361
|
+
{PalettePermissionRow["Read notebook", "ReadNotebook", $ActiveNotebookId]},
|
|
362
|
+
{PalettePermissionRow["Insert cell", "InsertCell", $ActiveNotebookId]},
|
|
363
|
+
{PalettePermissionRow["Modify cell", "ModifyCell", $ActiveNotebookId]},
|
|
364
|
+
{PalettePermissionRow["Delete cell", "DeleteCell", $ActiveNotebookId]},
|
|
365
|
+
{PalettePermissionRow["Run cell", "RunCell", $ActiveNotebookId]},
|
|
366
|
+
{PalettePermissionRow["Save notebook", "SaveNotebook", $ActiveNotebookId]},
|
|
367
|
+
{PalettePermissionRow["Create notebook", "CreateNotebook", $ActiveNotebookId]},
|
|
368
|
+
{PalettePermissionRow["Open notebook", "OpenNotebook", $ActiveNotebookId]}
|
|
343
369
|
},
|
|
344
370
|
Alignment -> Left,
|
|
345
371
|
Spacings -> {1, 0.35}
|
|
@@ -499,14 +525,14 @@ PaletteView[] := DynamicModule[{},
|
|
|
499
525
|
]
|
|
500
526
|
];
|
|
501
527
|
|
|
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
|
-
];
|
|
528
|
+
(* Re-read the small session file before each request so Wolfram follows MCP restarts and dynamic ports. *)
|
|
529
|
+
BridgeURL[path_String] := ConfigureBridgeFromSession[] <> path;
|
|
530
|
+
|
|
531
|
+
BridgeHeaders[] := If[
|
|
532
|
+
StringQ[$BridgeAuthToken] && StringLength[$BridgeAuthToken] > 0,
|
|
533
|
+
{"Authorization" -> "Bearer " <> $BridgeAuthToken},
|
|
534
|
+
{}
|
|
535
|
+
];
|
|
510
536
|
|
|
511
537
|
URLComponentEncodeString[None] := "";
|
|
512
538
|
URLComponentEncodeString[value_] := StringReplace[
|
|
@@ -564,8 +590,8 @@ JsonByteArrayToPayload[body_ByteArray] := Module[{text},
|
|
|
564
590
|
Quiet @ Check[ImportString[text, "RawJSON"], $Failed]
|
|
565
591
|
];
|
|
566
592
|
|
|
567
|
-
BridgeGet[path_String] := Module[{response},
|
|
568
|
-
response = BridgeRequestWithRetries[HTTPRequest[BridgeURL[path], <|"Method" -> "GET", "Headers" -> BridgeHeaders[]|>]];
|
|
593
|
+
BridgeGet[path_String] := Module[{response},
|
|
594
|
+
response = BridgeRequestWithRetries[HTTPRequest[BridgeURL[path], <|"Method" -> "GET", "Headers" -> BridgeHeaders[]|>]];
|
|
569
595
|
If[response === $Failed, Return[$Failed]];
|
|
570
596
|
JsonByteArrayToPayload[response["BodyByteArray"]]
|
|
571
597
|
];
|
|
@@ -575,9 +601,9 @@ BridgePost[path_String, payload_Association] := Module[{response},
|
|
|
575
601
|
HTTPRequest[
|
|
576
602
|
BridgeURL[path],
|
|
577
603
|
<|
|
|
578
|
-
"Method" -> "POST",
|
|
579
|
-
"Headers" -> BridgeHeaders[],
|
|
580
|
-
"ContentType" -> "application/json; charset=utf-8",
|
|
604
|
+
"Method" -> "POST",
|
|
605
|
+
"Headers" -> BridgeHeaders[],
|
|
606
|
+
"ContentType" -> "application/json; charset=utf-8",
|
|
581
607
|
"Body" -> PayloadToJsonBytes[payload]
|
|
582
608
|
|>
|
|
583
609
|
]
|
|
@@ -590,10 +616,16 @@ PostPermissions[] := Module[{payload = <|"permissions" -> $BridgePermissions|>},
|
|
|
590
616
|
Quiet @ Check[BridgePost["/permissions", payload], $Failed]
|
|
591
617
|
];
|
|
592
618
|
|
|
593
|
-
NeedsConfirmationQ[action_String] :=
|
|
619
|
+
NeedsConfirmationQ[action_String, notebookId_:None] := Module[{perms},
|
|
620
|
+
perms = If[StringQ[notebookId] && StringLength[notebookId] > 0,
|
|
621
|
+
NotebookPermissions[notebookId],
|
|
622
|
+
$BridgePermissions
|
|
623
|
+
];
|
|
624
|
+
Not @ TrueQ[perms[action]]
|
|
625
|
+
];
|
|
594
626
|
|
|
595
|
-
ConfirmAction[action_String, message_String] := If[
|
|
596
|
-
NeedsConfirmationQ[action],
|
|
627
|
+
ConfirmAction[action_String, message_String, notebookId_:None] := If[
|
|
628
|
+
NeedsConfirmationQ[action, notebookId],
|
|
597
629
|
ChoiceDialog[message, {"Allow" -> True, "Deny" -> False}],
|
|
598
630
|
True
|
|
599
631
|
];
|
|
@@ -606,8 +638,8 @@ FailedRequestCode[failure_Failure] := Module[{code = failure[[1]]},
|
|
|
606
638
|
|
|
607
639
|
FailedRequestMessage[failure_Failure] := Lookup[failure[[2]], "Message", Lookup[failure[[2]], "message", "The Wolfram bridge rejected the request."]];
|
|
608
640
|
|
|
609
|
-
RequireReadPermission[] := If[
|
|
610
|
-
Not @ ConfirmAction["ReadNotebook", "AI requests reading the notebook. Allow?"],
|
|
641
|
+
RequireReadPermission[notebookId_:None] := If[
|
|
642
|
+
Not @ ConfirmAction["ReadNotebook", "AI requests reading the notebook. Allow?", notebookId],
|
|
611
643
|
$Canceled,
|
|
612
644
|
True
|
|
613
645
|
];
|
|
@@ -628,7 +660,7 @@ NotebookInfo[nb_NotebookObject, notebookId_String] := <|
|
|
|
628
660
|
"wolframVersion" -> ToString @ $VersionNumber,
|
|
629
661
|
"platform" -> $OperatingSystem,
|
|
630
662
|
"paletteId" -> $PaletteId,
|
|
631
|
-
"permissions" ->
|
|
663
|
+
"permissions" -> NotebookPermissions[notebookId]
|
|
632
664
|
|>;
|
|
633
665
|
|
|
634
666
|
NotebookRecord[notebookId_String] := Lookup[$BridgeNotebooks, notebookId, <||>];
|
|
@@ -799,141 +831,141 @@ CellPayload[cell_CellObject, id_String, index_Integer] := <|
|
|
|
799
831
|
"tags" -> CellTagsList[cell]
|
|
800
832
|
|>;
|
|
801
833
|
|
|
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"};
|
|
834
|
+
CellGeneratedBoundaryQ[style_String] := MemberQ[{"Input", "Code", "Text", "Section", "Subsection", "Subsubsection", "Title", "Chapter"}, style];
|
|
835
|
+
|
|
836
|
+
CellArtifactStyleQ[style_String] := MemberQ[{"Output", "Print", "Message"}, style];
|
|
837
|
+
|
|
838
|
+
CellPayloadMaxBytes[args_Association] := Module[{value = Lookup[args, "maxBytes", $DefaultMaxCellPayloadBytes]},
|
|
839
|
+
If[IntegerQ[value] && value > 0 && value <= $MaxCellPayloadBytes, value, $DefaultMaxCellPayloadBytes]
|
|
840
|
+
];
|
|
841
|
+
|
|
842
|
+
Utf8LeadByteLength[byte_Integer] := Which[
|
|
843
|
+
byte < 128, 1,
|
|
844
|
+
byte < 224, 2,
|
|
845
|
+
byte < 240, 3,
|
|
846
|
+
byte < 248, 4,
|
|
847
|
+
True, 1
|
|
848
|
+
];
|
|
849
|
+
|
|
850
|
+
Utf8PrefixByteLength[bytes_List, maxBytes_Integer] := Module[{safeLength = Min[maxBytes, Length[bytes]], start, lead, expected},
|
|
851
|
+
If[safeLength <= 0, Return[0]];
|
|
852
|
+
start = safeLength;
|
|
853
|
+
While[start > 1 && bytes[[start]] >= 128 && bytes[[start]] <= 191, start--];
|
|
854
|
+
lead = bytes[[start]];
|
|
855
|
+
expected = Utf8LeadByteLength[lead];
|
|
856
|
+
If[safeLength - start + 1 >= expected, safeLength, Max[0, start - 1]]
|
|
857
|
+
];
|
|
858
|
+
|
|
859
|
+
TruncateStringToUtf8Bytes[text_String, maxBytes_Integer] := Module[{originalBytes, originalByteLength, safeLength, returnedText},
|
|
860
|
+
originalBytes = ToCharacterCode[text, "UTF8"];
|
|
861
|
+
originalByteLength = Length[originalBytes];
|
|
862
|
+
If[maxBytes <= 0,
|
|
863
|
+
Return[<|"value" -> "", "truncated" -> (originalByteLength > 0), "originalByteLength" -> originalByteLength, "returnedByteLength" -> 0|>]
|
|
864
|
+
];
|
|
865
|
+
If[originalByteLength <= maxBytes,
|
|
866
|
+
Return[<|"value" -> text, "truncated" -> False, "originalByteLength" -> originalByteLength, "returnedByteLength" -> originalByteLength|>]
|
|
867
|
+
];
|
|
868
|
+
safeLength = Utf8PrefixByteLength[originalBytes, maxBytes];
|
|
869
|
+
returnedText = If[safeLength > 0, ByteArrayToString[ByteArray[Take[originalBytes, safeLength]], "UTF8"], ""];
|
|
870
|
+
<|"value" -> returnedText, "truncated" -> True, "originalByteLength" -> originalByteLength, "returnedByteLength" -> safeLength|>
|
|
871
|
+
];
|
|
872
|
+
|
|
873
|
+
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},
|
|
874
|
+
processString[text_String] := Module[{item = TruncateStringToUtf8Bytes[text, remainingByteLength]},
|
|
875
|
+
totalOriginalByteLength += item["originalByteLength"];
|
|
876
|
+
totalReturnedByteLength += item["returnedByteLength"];
|
|
877
|
+
anyTruncated = anyTruncated || TrueQ[item["truncated"]];
|
|
878
|
+
remainingByteLength = Max[0, remainingByteLength - item["returnedByteLength"]];
|
|
879
|
+
item["value"]
|
|
880
|
+
];
|
|
881
|
+
If[TrueQ[includeContentQ], truncatedContent = processString[content]];
|
|
882
|
+
Do[
|
|
883
|
+
AppendTo[truncatedOutputs, processString[If[StringQ[output], output, ToString[output, InputForm]]]],
|
|
884
|
+
{output, outputs}
|
|
885
|
+
];
|
|
886
|
+
Do[
|
|
887
|
+
AppendTo[truncatedMessages, processString[If[StringQ[message], message, ToString[message, InputForm]]]],
|
|
888
|
+
{message, messages}
|
|
889
|
+
];
|
|
890
|
+
Join[
|
|
891
|
+
If[TrueQ[includeContentQ], <|"content" -> truncatedContent|>, <||>],
|
|
892
|
+
<|
|
|
893
|
+
"outputs" -> truncatedOutputs,
|
|
894
|
+
"messages" -> truncatedMessages,
|
|
895
|
+
"truncated" -> anyTruncated,
|
|
896
|
+
"originalByteLength" -> totalOriginalByteLength,
|
|
897
|
+
"returnedByteLength" -> totalReturnedByteLength
|
|
898
|
+
|>
|
|
899
|
+
]
|
|
900
|
+
];
|
|
901
|
+
|
|
902
|
+
ArtifactId[cellId_String, kind_String, index_Integer] := cellId <> ":" <> kind <> ":" <> ToString[index];
|
|
903
|
+
|
|
904
|
+
ArtifactDescriptor[cellId_String, kind_String, index_Integer, text_String, maxBytes_Integer] := Module[{byteLength, preview},
|
|
905
|
+
byteLength = Length[ToCharacterCode[text, "UTF8"]];
|
|
906
|
+
preview = TruncateStringToUtf8Bytes[text, maxBytes];
|
|
907
|
+
<|
|
|
908
|
+
"artifactId" -> ArtifactId[cellId, kind, index],
|
|
909
|
+
"type" -> kind,
|
|
910
|
+
"index" -> index,
|
|
911
|
+
"byteLength" -> byteLength,
|
|
912
|
+
"preview" -> preview["value"],
|
|
913
|
+
"previewByteLength" -> preview["returnedByteLength"],
|
|
914
|
+
"truncated" -> True
|
|
915
|
+
|>
|
|
916
|
+
];
|
|
917
|
+
|
|
918
|
+
ArtifactPayloadFields[cellId_String, outputs_List, messages_List, maxBytes_Integer] := Module[{remainingByteLength = maxBytes, totalOriginalByteLength = 0, totalReturnedByteLength = 0, anyTruncated = False, processedOutputs = {}, processedMessages = {}, processString, output, message},
|
|
919
|
+
processString[text_, kind_String, index_Integer] := Module[{value = If[StringQ[text], text, ToString[text, InputForm]], byteLength, descriptor},
|
|
920
|
+
byteLength = Length[ToCharacterCode[value, "UTF8"]];
|
|
921
|
+
totalOriginalByteLength += byteLength;
|
|
922
|
+
If[byteLength <= remainingByteLength,
|
|
923
|
+
totalReturnedByteLength += byteLength;
|
|
924
|
+
remainingByteLength = Max[0, remainingByteLength - byteLength];
|
|
925
|
+
value,
|
|
926
|
+
anyTruncated = True;
|
|
927
|
+
descriptor = ArtifactDescriptor[cellId, kind, index, value, remainingByteLength];
|
|
928
|
+
totalReturnedByteLength += descriptor["previewByteLength"];
|
|
929
|
+
remainingByteLength = Max[0, remainingByteLength - descriptor["previewByteLength"]];
|
|
930
|
+
descriptor
|
|
931
|
+
]
|
|
932
|
+
];
|
|
933
|
+
Do[
|
|
934
|
+
AppendTo[processedOutputs, processString[outputs[[i]], "output", i - 1]],
|
|
935
|
+
{i, Length[outputs]}
|
|
936
|
+
];
|
|
937
|
+
Do[
|
|
938
|
+
AppendTo[processedMessages, processString[messages[[i]], "message", i - 1]],
|
|
939
|
+
{i, Length[messages]}
|
|
940
|
+
];
|
|
941
|
+
<|
|
|
942
|
+
"outputs" -> processedOutputs,
|
|
943
|
+
"messages" -> processedMessages,
|
|
944
|
+
"truncated" -> anyTruncated,
|
|
945
|
+
"originalByteLength" -> totalOriginalByteLength,
|
|
946
|
+
"returnedByteLength" -> totalReturnedByteLength
|
|
947
|
+
|>
|
|
948
|
+
];
|
|
949
|
+
|
|
950
|
+
Utf8SliceStringToBytes[text_String, offset_Integer, limit_Integer] := Module[{bytes, originalByteLength, start, available, safeLength, sliceBytes, value},
|
|
951
|
+
bytes = ToCharacterCode[text, "UTF8"];
|
|
952
|
+
originalByteLength = Length[bytes];
|
|
953
|
+
If[limit <= 0 || offset >= originalByteLength,
|
|
954
|
+
Return[<|"value" -> "", "originalByteLength" -> originalByteLength, "returnedByteLength" -> 0, "nextOffset" -> originalByteLength|>]
|
|
955
|
+
];
|
|
956
|
+
start = Max[1, offset + 1];
|
|
957
|
+
While[start <= originalByteLength && bytes[[start]] >= 128 && bytes[[start]] <= 191, start++];
|
|
958
|
+
available = originalByteLength - start + 1;
|
|
959
|
+
If[available <= 0,
|
|
960
|
+
Return[<|"value" -> "", "originalByteLength" -> originalByteLength, "returnedByteLength" -> 0, "nextOffset" -> originalByteLength|>]
|
|
961
|
+
];
|
|
962
|
+
safeLength = Utf8PrefixByteLength[Take[bytes, {start, originalByteLength}], Min[limit, available]];
|
|
963
|
+
sliceBytes = If[safeLength > 0, Take[bytes, {start, start + safeLength - 1}], {}];
|
|
964
|
+
value = If[safeLength > 0, ByteArrayToString[ByteArray[sliceBytes], "UTF8"], ""];
|
|
965
|
+
<|"value" -> value, "originalByteLength" -> originalByteLength, "returnedByteLength" -> safeLength, "nextOffset" -> (start - 1 + safeLength)|>
|
|
966
|
+
];
|
|
967
|
+
|
|
968
|
+
CellEvaluationTaggingPath[cellId_String] := {TaggingRules, "MMAAgentBridge", "evaluations", cellId, "complete"};
|
|
937
969
|
|
|
938
970
|
MarkCellEvaluationComplete[cellId_String] := Quiet @ Check[
|
|
939
971
|
CurrentValue[EvaluationNotebook[], CellEvaluationTaggingPath[cellId]] = True,
|
|
@@ -1011,23 +1043,23 @@ RestoreRunningCellEpilog[] := Module[{},
|
|
|
1011
1043
|
ClearRunningEvaluationState[] := Module[{cellId = $RunningCellId, notebook = $RunningNotebookObject},
|
|
1012
1044
|
RestoreRunningCellEpilog[];
|
|
1013
1045
|
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
|
-
];
|
|
1046
|
+
$RunningCellId = None;
|
|
1047
|
+
$RunningRequestId = None;
|
|
1048
|
+
$RunningNotebookId = None;
|
|
1049
|
+
$RunningNotebookObject = None;
|
|
1050
|
+
$RunningStartedAt = None;
|
|
1051
|
+
$RunningStatus = None;
|
|
1052
|
+
$AbortRequestedAt = None;
|
|
1053
|
+
$RunningTimeoutAt = None
|
|
1054
|
+
];
|
|
1055
|
+
|
|
1056
|
+
FinishRunningCell[status_String] := Module[{cellId = $RunningCellId, notebookId = $RunningNotebookId},
|
|
1057
|
+
ClearRunningEvaluationState[];
|
|
1058
|
+
$LastRunStatusCellId = cellId;
|
|
1059
|
+
$LastRunStatusNotebookId = notebookId;
|
|
1060
|
+
$RunningStatus = status;
|
|
1061
|
+
$LastRunStatus = status
|
|
1062
|
+
];
|
|
1031
1063
|
|
|
1032
1064
|
CellGeneratedArtifactQ[cell_CellObject] := TrueQ[Quiet @ Check[CurrentValue[cell, GeneratedCell], False]] && CellArtifactStyleQ[CellStyleName[cell]];
|
|
1033
1065
|
|
|
@@ -1087,14 +1119,14 @@ CellArtifactScan[cell_CellObject, cellId_String:"", notebook_:Automatic] := Modu
|
|
|
1087
1119
|
"timeout",
|
|
1088
1120
|
StringQ[cellId] && StringQ[$LastRunStatusCellId] && cellId === $LastRunStatusCellId && StringQ[$LastRunStatusNotebookId] && NotebookIdForObject[nb] === $LastRunStatusNotebookId && $LastRunStatus === "finished",
|
|
1089
1121
|
"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",
|
|
1122
|
+
StringQ[cellId] && StringQ[$LastRunStatusCellId] && cellId === $LastRunStatusCellId && StringQ[$LastRunStatusNotebookId] && NotebookIdForObject[nb] === $LastRunStatusNotebookId && $LastRunStatus === "aborted",
|
|
1123
|
+
"aborted",
|
|
1124
|
+
sameRunningCellQ && NumberQ[$AbortRequestedAt] && !evaluationCompleteQ,
|
|
1125
|
+
"abort_requested",
|
|
1126
|
+
sameRunningCellQ && NumberQ[$AbortRequestedAt] && evaluationCompleteQ,
|
|
1127
|
+
(FinishRunningCell["aborted"]; "aborted"),
|
|
1128
|
+
sameRunningCellQ && NumberQ[$RunningStartedAt] && (AbsoluteTime[] - $RunningStartedAt < $RunningStatusGraceSeconds),
|
|
1129
|
+
"running",
|
|
1098
1130
|
sameRunningCellQ && (evaluationCompleteQ || hasFinalOutputQ),
|
|
1099
1131
|
(FinishRunningCell["finished"]; "finished"),
|
|
1100
1132
|
sameRunningCellQ,
|
|
@@ -1137,86 +1169,86 @@ RefreshCellMap[notebookId_String] := Module[{record, nb, cells, idByCell, previo
|
|
|
1137
1169
|
payload
|
|
1138
1170
|
];
|
|
1139
1171
|
|
|
1140
|
-
ReadCellById[args_Association] := Module[{notebookId, record, cellId, cell, maxBytes, payload},
|
|
1141
|
-
|
|
1142
|
-
notebookId
|
|
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
|
-
|
|
1163
|
-
notebookId
|
|
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."|>]]];
|
|
1172
|
+
ReadCellById[args_Association] := Module[{notebookId, record, cellId, cell, maxBytes, payload},
|
|
1173
|
+
notebookId = TargetNotebookId[args];
|
|
1174
|
+
If[RequireReadPermission[notebookId] === $Canceled, Return[$Canceled]];
|
|
1175
|
+
If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
|
|
1176
|
+
record = NotebookRecord[notebookId];
|
|
1177
|
+
If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
|
|
1178
|
+
cellId = Lookup[args, "cellId", ""];
|
|
1179
|
+
If[StringLength[cellId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "cellId is required."|>]]];
|
|
1180
|
+
cell = Lookup[Lookup[record, "cellMap", <||>], cellId, Missing["NotFound"]];
|
|
1181
|
+
If[MissingQ[cell], Return[Failure["BAD_REQUEST", <|"message" -> "Requested cell was not found."|>]]];
|
|
1182
|
+
maxBytes = CellPayloadMaxBytes[args];
|
|
1183
|
+
With[{artifacts = CellArtifactScan[cell, cellId, Lookup[record, "notebook", None]]},
|
|
1184
|
+
payload = TruncatePayloadFields[CellContentString[cell], artifacts["outputs"], artifacts["messages"], maxBytes, True];
|
|
1185
|
+
Join[
|
|
1186
|
+
<|"cellId" -> cellId, "style" -> CellStyleName[cell]|>,
|
|
1187
|
+
payload,
|
|
1188
|
+
<|"status" -> artifacts["status"]|>
|
|
1189
|
+
]
|
|
1190
|
+
]
|
|
1191
|
+
];
|
|
1192
|
+
|
|
1193
|
+
GetCellOutputById[args_Association] := Module[{notebookId, record, cellId, cell, artifacts, maxBytes, payload},
|
|
1194
|
+
notebookId = TargetNotebookId[args];
|
|
1195
|
+
If[RequireReadPermission[notebookId] === $Canceled, Return[$Canceled]];
|
|
1196
|
+
If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
|
|
1197
|
+
record = NotebookRecord[notebookId];
|
|
1198
|
+
If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
|
|
1167
1199
|
cellId = Lookup[args, "cellId", ""];
|
|
1168
1200
|
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
|
-
|
|
1183
|
-
notebookId
|
|
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"]},
|
|
1201
|
+
cell = Lookup[Lookup[record, "cellMap", <||>], cellId, Missing["NotFound"]];
|
|
1202
|
+
If[MissingQ[cell], Return[Failure["BAD_REQUEST", <|"message" -> "Requested cell was not found."|>]]];
|
|
1203
|
+
maxBytes = CellPayloadMaxBytes[args];
|
|
1204
|
+
artifacts = CellArtifactScan[cell, cellId, Lookup[record, "notebook", None]];
|
|
1205
|
+
payload = ArtifactPayloadFields[cellId, artifacts["outputs"], artifacts["messages"], maxBytes];
|
|
1206
|
+
Join[
|
|
1207
|
+
<|"cellId" -> cellId|>,
|
|
1208
|
+
payload,
|
|
1209
|
+
<|"status" -> artifacts["status"]|>
|
|
1210
|
+
]
|
|
1211
|
+
];
|
|
1212
|
+
|
|
1213
|
+
ReadArtifactById[args_Association] := Module[{notebookId, record, artifactId, parts, cellId, kind, indexText, index, cell, artifacts, artifactList, text, offset, limit, page, nextOffset, done},
|
|
1214
|
+
notebookId = TargetNotebookId[args];
|
|
1215
|
+
If[RequireReadPermission[notebookId] === $Canceled, Return[$Canceled]];
|
|
1216
|
+
If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
|
|
1217
|
+
record = NotebookRecord[notebookId];
|
|
1218
|
+
If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
|
|
1219
|
+
artifactId = Lookup[args, "artifactId", ""];
|
|
1220
|
+
If[!StringQ[artifactId] || StringLength[artifactId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "artifactId is required."|>]]];
|
|
1221
|
+
parts = StringSplit[artifactId, ":"];
|
|
1222
|
+
If[Length[parts] =!= 3, Return[Failure["BAD_REQUEST", <|"message" -> "artifactId must have the form cellId:type:index."|>]]];
|
|
1223
|
+
{cellId, kind, indexText} = parts;
|
|
1224
|
+
If[!MemberQ[{"output", "message"}, kind], Return[Failure["BAD_REQUEST", <|"message" -> "artifact type must be output or message."|>]]];
|
|
1225
|
+
If[!StringMatchQ[indexText, DigitCharacter..], Return[Failure["BAD_REQUEST", <|"message" -> "artifact index must be a non-negative integer."|>]]];
|
|
1226
|
+
index = ToExpression[indexText];
|
|
1227
|
+
offset = Lookup[args, "offset", 0];
|
|
1228
|
+
limit = Lookup[args, "limit", 65536];
|
|
1229
|
+
If[!IntegerQ[offset] || offset < 0, Return[Failure["BAD_REQUEST", <|"message" -> "offset must be a non-negative integer."|>]]];
|
|
1230
|
+
If[!IntegerQ[limit] || limit <= 0 || limit > $MaxCellPayloadBytes, Return[Failure["BAD_REQUEST", <|"message" -> "limit must be a positive integer up to 1 MiB."|>]]];
|
|
1231
|
+
cell = Lookup[Lookup[record, "cellMap", <||>], cellId, Missing["NotFound"]];
|
|
1232
|
+
If[MissingQ[cell], Return[Failure["BAD_REQUEST", <|"message" -> "Requested cell was not found."|>]]];
|
|
1233
|
+
artifacts = CellArtifactScan[cell, cellId, Lookup[record, "notebook", None]];
|
|
1234
|
+
artifactList = If[kind === "output", artifacts["outputs"], artifacts["messages"]];
|
|
1235
|
+
If[index >= Length[artifactList], Return[Failure["BAD_REQUEST", <|"message" -> "Requested artifact was not found."|>]]];
|
|
1236
|
+
text = artifactList[[index + 1]];
|
|
1237
|
+
page = Utf8SliceStringToBytes[text, offset, limit];
|
|
1238
|
+
nextOffset = page["nextOffset"];
|
|
1239
|
+
done = nextOffset >= page["originalByteLength"];
|
|
1240
|
+
<|
|
|
1241
|
+
"artifactId" -> artifactId,
|
|
1242
|
+
"offset" -> offset,
|
|
1243
|
+
"limit" -> limit,
|
|
1244
|
+
"data" -> page["value"],
|
|
1245
|
+
"nextOffset" -> nextOffset,
|
|
1246
|
+
"done" -> done,
|
|
1247
|
+
"byteLength" -> page["originalByteLength"]
|
|
1248
|
+
|>
|
|
1249
|
+
];
|
|
1250
|
+
|
|
1251
|
+
MakeCellExpression[content_String, style_String] := Module[{cellStyle = If[StringQ[style] && StringLength[style] > 0, style, "Input"]},
|
|
1220
1252
|
If[cellStyle === "Input",
|
|
1221
1253
|
Cell[BoxData[content], "Input", CellTags -> {"AI-Generated"}],
|
|
1222
1254
|
Cell[content, cellStyle, CellTags -> {"AI-Generated"}]
|
|
@@ -1255,8 +1287,8 @@ InsertCellAtBeginning[notebook_NotebookObject, newCell_] := Module[{beforeCount,
|
|
|
1255
1287
|
];
|
|
1256
1288
|
|
|
1257
1289
|
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
1290
|
notebookId = TargetNotebookId[args];
|
|
1291
|
+
If[Not @ ConfirmAction["InsertCell", "AI requests inserting 1 cell. Allow?", notebookId], Return[$Canceled]];
|
|
1260
1292
|
If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
|
|
1261
1293
|
record = NotebookRecord[notebookId];
|
|
1262
1294
|
If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
|
|
@@ -1291,8 +1323,8 @@ InsertCellRequest[args_Association] := Module[{notebookId, record, afterId, styl
|
|
|
1291
1323
|
];
|
|
1292
1324
|
|
|
1293
1325
|
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
1326
|
notebookId = TargetNotebookId[args];
|
|
1327
|
+
If[Not @ ConfirmAction["ModifyCell", "AI requests modifying 1 cell. Allow?", notebookId], Return[$Canceled]];
|
|
1296
1328
|
If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
|
|
1297
1329
|
record = NotebookRecord[notebookId];
|
|
1298
1330
|
If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
|
|
@@ -1322,8 +1354,8 @@ ModifyCellRequest[args_Association] := Module[{notebookId, record, notebook, cel
|
|
|
1322
1354
|
];
|
|
1323
1355
|
|
|
1324
1356
|
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
1357
|
notebookId = TargetNotebookId[args];
|
|
1358
|
+
If[Not @ ConfirmAction["DeleteCell", "AI requests deleting 1 cell. Allow?", notebookId], Return[$Canceled]];
|
|
1327
1359
|
If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
|
|
1328
1360
|
record = NotebookRecord[notebookId];
|
|
1329
1361
|
If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
|
|
@@ -1345,8 +1377,8 @@ DeleteCellRequest[args_Association] := Module[{notebookId, record, notebook, cel
|
|
|
1345
1377
|
];
|
|
1346
1378
|
|
|
1347
1379
|
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
1380
|
notebookId = TargetNotebookId[args];
|
|
1381
|
+
If[Not @ ConfirmAction["RunCell", "AI requests running 1 cell. Allow?", notebookId], Return[$Canceled]];
|
|
1350
1382
|
If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
|
|
1351
1383
|
record = NotebookRecord[notebookId];
|
|
1352
1384
|
If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
|
|
@@ -1358,14 +1390,14 @@ RunCellRequest[args_Association] := Module[{notebookId, record, notebook, cellId
|
|
|
1358
1390
|
If[MissingQ[cell], Return[Failure["BAD_REQUEST", <|"message" -> "Requested cell was not found."|>]]];
|
|
1359
1391
|
ClearCellEvaluationComplete[notebook, cellId];
|
|
1360
1392
|
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];
|
|
1393
|
+
If[MatchQ[installedEpilog, _Failure], Return[installedEpilog]];
|
|
1394
|
+
$LastRunStatusCellId = None;
|
|
1395
|
+
$LastRunStatusNotebookId = None;
|
|
1396
|
+
$LastRunStatus = None;
|
|
1397
|
+
$RunningStatus = "running";
|
|
1398
|
+
$AbortRequestedAt = None;
|
|
1399
|
+
$LastLateResult = None;
|
|
1400
|
+
$RunningRequestId = Lookup[args, "requestId", $CurrentRequestId];
|
|
1369
1401
|
$RunningCellId = cellId;
|
|
1370
1402
|
$RunningNotebookId = notebookId;
|
|
1371
1403
|
$RunningNotebookObject = notebook;
|
|
@@ -1384,8 +1416,8 @@ RunCellRequest[args_Association] := Module[{notebookId, record, notebook, cellId
|
|
|
1384
1416
|
];
|
|
1385
1417
|
|
|
1386
1418
|
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
1419
|
notebookId = TargetNotebookId[args];
|
|
1420
|
+
If[Not @ ConfirmAction["RunCell", "AI requests aborting the running evaluation. Allow?", notebookId], Return[$Canceled]];
|
|
1389
1421
|
If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
|
|
1390
1422
|
record = NotebookRecord[notebookId];
|
|
1391
1423
|
If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
|
|
@@ -1398,15 +1430,15 @@ AbortEvaluationRequest[args_Association] := Module[{notebookId, record, notebook
|
|
|
1398
1430
|
FinishRunningCell["finished"];
|
|
1399
1431
|
Return[<|"status" -> "finished", "cellId" -> runningCellId, "requestId" -> runningRequestId|>]
|
|
1400
1432
|
];
|
|
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
|
-
]
|
|
1433
|
+
If[wasRunning,
|
|
1434
|
+
$AbortRequestedAt = AbsoluteTime[];
|
|
1435
|
+
$RunningStatus = "abort_requested"
|
|
1436
|
+
];
|
|
1437
|
+
Quiet @ Check[FrontEndTokenExecute[notebook, "EvaluatorAbort"], Null];
|
|
1438
|
+
If[wasRunning,
|
|
1439
|
+
<|"status" -> "abort_requested", "cellId" -> runningCellId, "requestId" -> runningRequestId|>,
|
|
1440
|
+
<|"status" -> "idle"|>
|
|
1441
|
+
]
|
|
1410
1442
|
];
|
|
1411
1443
|
|
|
1412
1444
|
KillKernelRequest[args_Association] := Module[{notebookId, record, notebook, evaluatorName},
|
|
@@ -1435,13 +1467,48 @@ RestartKernelRequest[args_Association] := Module[{notebookId, record, notebook,
|
|
|
1435
1467
|
If[evaluatorName === $ControlAgentEvaluatorName,
|
|
1436
1468
|
Return[Failure["PROTECTED_EVALUATOR", <|"message" -> "Cannot restart the MICA control agent evaluator."|>]]
|
|
1437
1469
|
];
|
|
1470
|
+
Quiet @ Check[NotebookEvaluate[notebook, "Quit[]", InsertResults -> False], Null];
|
|
1471
|
+
Pause[0.5];
|
|
1438
1472
|
Quiet @ Check[NotebookEvaluate[notebook, "Null", InsertResults -> False], Null];
|
|
1439
1473
|
<|"status" -> "restarted", "notebookId" -> notebookId|>
|
|
1440
1474
|
];
|
|
1441
1475
|
|
|
1442
|
-
|
|
1443
|
-
|
|
1476
|
+
CreateNotebookRequest[args_Association] := Module[{title, nb, notebookId, payload, response},
|
|
1477
|
+
title = Lookup[args, "title", "Untitled"];
|
|
1478
|
+
If[!StringQ[title] || StringLength[StringTrim[title]] == 0, title = "Untitled"];
|
|
1479
|
+
nb = Quiet @ Check[CreateDocument[{}, WindowTitle -> title, Visible -> True], $Failed];
|
|
1480
|
+
If[Head[nb] =!= NotebookObject, Return[Failure["CREATE_FAILED", <|"message" -> "The FrontEnd failed to create the notebook."|>]]];
|
|
1481
|
+
payload = NotebookHeartbeatPayload[nb];
|
|
1482
|
+
response = Quiet @ Check[BridgePost["/notebooks/heartbeat", payload], $Failed];
|
|
1483
|
+
notebookId = If[AssociationQ[response],
|
|
1484
|
+
Lookup[Lookup[response, "notebook", <||>], "notebookId", Lookup[response, "notebookId", None]],
|
|
1485
|
+
None
|
|
1486
|
+
];
|
|
1487
|
+
<|"status" -> "created", "notebookId" -> notebookId, "title" -> title|>
|
|
1488
|
+
];
|
|
1489
|
+
|
|
1490
|
+
OpenNotebookRequest[args_Association] := Module[{path, nb, notebookId, payload, response},
|
|
1491
|
+
path = Lookup[args, "path", ""];
|
|
1492
|
+
If[!StringQ[path] || StringLength[StringTrim[path]] == 0,
|
|
1493
|
+
Return[Failure["BAD_REQUEST", <|"message" -> "path is required."|>]]
|
|
1494
|
+
];
|
|
1495
|
+
If[!FileExistsQ[path],
|
|
1496
|
+
Return[Failure["NOT_FOUND", <|"message" -> "File not found: " <> path|>]]
|
|
1497
|
+
];
|
|
1498
|
+
nb = Quiet @ Check[NotebookOpen[path, Visible -> True], $Failed];
|
|
1499
|
+
If[Head[nb] =!= NotebookObject, Return[Failure["OPEN_FAILED", <|"message" -> "The FrontEnd failed to open the notebook."|>]]];
|
|
1500
|
+
payload = NotebookHeartbeatPayload[nb];
|
|
1501
|
+
response = Quiet @ Check[BridgePost["/notebooks/heartbeat", payload], $Failed];
|
|
1502
|
+
notebookId = If[AssociationQ[response],
|
|
1503
|
+
Lookup[Lookup[response, "notebook", <||>], "notebookId", Lookup[response, "notebookId", None]],
|
|
1504
|
+
None
|
|
1505
|
+
];
|
|
1506
|
+
<|"status" -> "opened", "notebookId" -> notebookId, "path" -> path|>
|
|
1507
|
+
];
|
|
1508
|
+
|
|
1509
|
+
SaveNotebookRequest[args_Association] := Module[{notebookId, record, notebook},
|
|
1444
1510
|
notebookId = TargetNotebookId[args];
|
|
1511
|
+
If[Not @ ConfirmAction["SaveNotebook", "AI requests saving the notebook. Allow?", notebookId], Return[$Canceled]];
|
|
1445
1512
|
If[!StringQ[notebookId] || StringLength[notebookId] == 0, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
|
|
1446
1513
|
record = NotebookRecord[notebookId];
|
|
1447
1514
|
If[!AssociationQ[record] || record === <||>, Return[Failure["BAD_REQUEST", <|"message" -> "No notebook is selected."|>]]];
|
|
@@ -1496,11 +1563,15 @@ AgentHeartbeat[] := BridgePost[
|
|
|
1496
1563
|
|>
|
|
1497
1564
|
];
|
|
1498
1565
|
|
|
1499
|
-
NotebookHeartbeatPayload[nb_NotebookObject] := Module[{savedPath, windowTitle, displayName, frontendObjectKey},
|
|
1566
|
+
NotebookHeartbeatPayload[nb_NotebookObject, notebookId_:None] := Module[{savedPath, windowTitle, displayName, frontendObjectKey, perms},
|
|
1500
1567
|
savedPath = Quiet @ Check[ToString[Replace[NotebookFileName[nb], $Failed -> ""]], ""];
|
|
1501
1568
|
frontendObjectKey = FrontendObjectKey[nb];
|
|
1502
1569
|
windowTitle = NotebookWindowTitle[nb];
|
|
1503
1570
|
displayName = NotebookDisplayNameForHeartbeat[nb, savedPath, frontendObjectKey];
|
|
1571
|
+
perms = If[StringQ[notebookId] && StringLength[notebookId] > 0,
|
|
1572
|
+
NotebookPermissions[notebookId],
|
|
1573
|
+
$DefaultBridgePermissions
|
|
1574
|
+
];
|
|
1504
1575
|
<|
|
|
1505
1576
|
"agentSessionId" -> $AgentSessionId,
|
|
1506
1577
|
"frontendObjectKey" -> frontendObjectKey,
|
|
@@ -1510,7 +1581,7 @@ NotebookHeartbeatPayload[nb_NotebookObject] := Module[{savedPath, windowTitle, d
|
|
|
1510
1581
|
"savedPath" -> savedPath,
|
|
1511
1582
|
"wolframVersion" -> ToString[$VersionNumber],
|
|
1512
1583
|
"platform" -> $OperatingSystem,
|
|
1513
|
-
"permissions" ->
|
|
1584
|
+
"permissions" -> perms,
|
|
1514
1585
|
"seenAt" -> UnixTimeMilliseconds[]
|
|
1515
1586
|
|>
|
|
1516
1587
|
];
|
|
@@ -1544,7 +1615,8 @@ AgentHeartbeatNotebookClosure[notebookId_String] := Module[{record = Lookup[$Bri
|
|
|
1544
1615
|
response = Quiet @ Check[BridgePost["/notebooks/" <> URLComponentEncodeString[notebookId] <> "/closed", <|"agentSessionId" -> $AgentSessionId|>], $Failed];
|
|
1545
1616
|
If[AssociationQ[record] && AssociationQ[response] && TrueQ[Lookup[response, "ok", False]],
|
|
1546
1617
|
If[!TrueQ[Lookup[record, "closed", False]],
|
|
1547
|
-
$BridgeNotebooks[notebookId] = Join[record, <|"closed" -> True|>]
|
|
1618
|
+
$BridgeNotebooks[notebookId] = Join[record, <|"closed" -> True|>];
|
|
1619
|
+
KeyDropFrom[$BridgeNotebookPermissions, notebookId]
|
|
1548
1620
|
]
|
|
1549
1621
|
];
|
|
1550
1622
|
response
|
|
@@ -1568,7 +1640,7 @@ HeartbeatNotebooks[] := Module[{notebooks = AgentVisibleNotebooks[], visibleNote
|
|
|
1568
1640
|
None
|
|
1569
1641
|
];
|
|
1570
1642
|
If[StringQ[localNotebookId] && StringLength[localNotebookId] > 0, AppendTo[visibleNotebookIds, localNotebookId]];
|
|
1571
|
-
payload = NotebookHeartbeatPayload[nb];
|
|
1643
|
+
payload = NotebookHeartbeatPayload[nb, localNotebookId];
|
|
1572
1644
|
response = Quiet @ Check[BridgePost["/notebooks/heartbeat", payload], $Failed];
|
|
1573
1645
|
If[AssociationQ[response],
|
|
1574
1646
|
notebookId = Lookup[Lookup[response, "notebook", <||>], "notebookId", Lookup[response, "notebookId", None]];
|
|
@@ -1801,18 +1873,20 @@ ExecuteRequest[request_Association] := Module[{requestId, tool, args, result},
|
|
|
1801
1873
|
!AssociationQ[args], Failure["BAD_REQUEST", <|"Message" -> "Request arguments must be an association."|>],
|
|
1802
1874
|
True,
|
|
1803
1875
|
Switch[tool,
|
|
1804
|
-
"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|>]]]],
|
|
1876
|
+
"mma_list_cells", If[RequireReadPermission[TargetNotebookId[args]] === $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|>]]]],
|
|
1805
1877
|
"mma_read_cell", ReadCellById[args],
|
|
1806
1878
|
"mma_insert_cell", InsertCellRequest[args],
|
|
1807
1879
|
"mma_modify_cell", ModifyCellRequest[args],
|
|
1808
1880
|
"mma_delete_cell", DeleteCellRequest[args],
|
|
1809
|
-
"mma_run_cell", RunCellRequest[args],
|
|
1881
|
+
"mma_run_cell", RunCellRequest[args],
|
|
1810
1882
|
"mma_abort_evaluation", AbortEvaluationRequest[args],
|
|
1811
1883
|
"mma_kill_kernel", KillKernelRequest[args],
|
|
1812
|
-
"mma_restart_kernel", RestartKernelRequest[args],
|
|
1813
|
-
"
|
|
1814
|
-
"
|
|
1815
|
-
"
|
|
1884
|
+
"mma_restart_kernel", RestartKernelRequest[args],
|
|
1885
|
+
"mma_create_notebook", CreateNotebookRequest[args],
|
|
1886
|
+
"mma_open_notebook", OpenNotebookRequest[args],
|
|
1887
|
+
"mma_get_cell_output", GetCellOutputById[args],
|
|
1888
|
+
"mma_read_artifact", ReadArtifactById[args],
|
|
1889
|
+
"mma_save_notebook", SaveNotebookRequest[args],
|
|
1816
1890
|
"mma_select_notebook", SelectNotebookRequest[args],
|
|
1817
1891
|
"mma_symbol_lookup", SymbolLookup[Lookup[args, "query", ""]],
|
|
1818
1892
|
_, Failure["BAD_REQUEST", <|"Message" -> StringTemplate["Unknown tool ``."][tool]|>]
|