nebula-notebook 0.1.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/README.md +222 -0
- package/bin/nebula-notebook.js +33 -0
- package/dist/assets/index-C1h_sArD.css +32 -0
- package/dist/assets/index-CDSTBon8.js +658 -0
- package/dist/favicon.svg +11 -0
- package/dist/index.html +73 -0
- package/node-server/dist/app.d.ts +5 -0
- package/node-server/dist/app.js +38 -0
- package/node-server/dist/auth/auth-middleware.d.ts +24 -0
- package/node-server/dist/auth/auth-middleware.js +276 -0
- package/node-server/dist/auth/auth-service.d.ts +84 -0
- package/node-server/dist/auth/auth-service.js +265 -0
- package/node-server/dist/auth/index.d.ts +3 -0
- package/node-server/dist/auth/index.js +8 -0
- package/node-server/dist/cluster/client-registration.d.ts +43 -0
- package/node-server/dist/cluster/client-registration.js +217 -0
- package/node-server/dist/cluster/cluster-secret.d.ts +11 -0
- package/node-server/dist/cluster/cluster-secret.js +90 -0
- package/node-server/dist/cluster/kernel-proxy.d.ts +100 -0
- package/node-server/dist/cluster/kernel-proxy.js +361 -0
- package/node-server/dist/cluster/server-registry.d.ts +109 -0
- package/node-server/dist/cluster/server-registry.js +217 -0
- package/node-server/dist/config/output-limits.d.ts +7 -0
- package/node-server/dist/config/output-limits.js +10 -0
- package/node-server/dist/discovery/discovery-service.d.ts +198 -0
- package/node-server/dist/discovery/discovery-service.js +811 -0
- package/node-server/dist/discovery/index.d.ts +5 -0
- package/node-server/dist/discovery/index.js +21 -0
- package/node-server/dist/discovery/types.d.ts +48 -0
- package/node-server/dist/discovery/types.js +24 -0
- package/node-server/dist/fs/fs-service.d.ts +218 -0
- package/node-server/dist/fs/fs-service.js +1422 -0
- package/node-server/dist/fs/index.d.ts +5 -0
- package/node-server/dist/fs/index.js +21 -0
- package/node-server/dist/fs/types.d.ts +132 -0
- package/node-server/dist/fs/types.js +5 -0
- package/node-server/dist/index.d.ts +13 -0
- package/node-server/dist/index.js +556 -0
- package/node-server/dist/kernel/default-kernel.d.ts +5 -0
- package/node-server/dist/kernel/default-kernel.js +138 -0
- package/node-server/dist/kernel/index.d.ts +7 -0
- package/node-server/dist/kernel/index.js +23 -0
- package/node-server/dist/kernel/kernel-service.d.ts +290 -0
- package/node-server/dist/kernel/kernel-service.js +1714 -0
- package/node-server/dist/kernel/kernelspec.d.ts +29 -0
- package/node-server/dist/kernel/kernelspec.js +210 -0
- package/node-server/dist/kernel/session-store.d.ts +87 -0
- package/node-server/dist/kernel/session-store.js +303 -0
- package/node-server/dist/kernel/types.d.ts +143 -0
- package/node-server/dist/kernel/types.js +17 -0
- package/node-server/dist/llm/index.d.ts +5 -0
- package/node-server/dist/llm/index.js +21 -0
- package/node-server/dist/llm/llm-service.d.ts +77 -0
- package/node-server/dist/llm/llm-service.js +454 -0
- package/node-server/dist/llm/types.d.ts +40 -0
- package/node-server/dist/llm/types.js +15 -0
- package/node-server/dist/notebook/cell-metadata.d.ts +27 -0
- package/node-server/dist/notebook/cell-metadata.js +76 -0
- package/node-server/dist/notebook/headless-handler.d.ts +127 -0
- package/node-server/dist/notebook/headless-handler.js +1530 -0
- package/node-server/dist/notebook/notebook-websocket.d.ts +12 -0
- package/node-server/dist/notebook/notebook-websocket.js +103 -0
- package/node-server/dist/notebook/operation-router.d.ts +115 -0
- package/node-server/dist/notebook/operation-router.js +641 -0
- package/node-server/dist/notebook/undoRedoManager.d.ts +194 -0
- package/node-server/dist/notebook/undoRedoManager.js +558 -0
- package/node-server/dist/output/display-data.d.ts +14 -0
- package/node-server/dist/output/display-data.js +134 -0
- package/node-server/dist/resources/resource-service.d.ts +69 -0
- package/node-server/dist/resources/resource-service.js +363 -0
- package/node-server/dist/routes/auth.d.ts +5 -0
- package/node-server/dist/routes/auth.js +61 -0
- package/node-server/dist/routes/cluster.d.ts +7 -0
- package/node-server/dist/routes/cluster.js +94 -0
- package/node-server/dist/routes/fs.d.ts +7 -0
- package/node-server/dist/routes/fs.js +392 -0
- package/node-server/dist/routes/kernel.d.ts +13 -0
- package/node-server/dist/routes/kernel.js +637 -0
- package/node-server/dist/routes/llm.d.ts +8 -0
- package/node-server/dist/routes/llm.js +105 -0
- package/node-server/dist/routes/notebook.d.ts +10 -0
- package/node-server/dist/routes/notebook.js +335 -0
- package/node-server/dist/routes/python.d.ts +8 -0
- package/node-server/dist/routes/python.js +187 -0
- package/node-server/dist/routes/resources.d.ts +7 -0
- package/node-server/dist/routes/resources.js +77 -0
- package/node-server/dist/scripts/show-auth-qr.d.ts +1 -0
- package/node-server/dist/scripts/show-auth-qr.js +81 -0
- package/node-server/dist/terminal/pty-manager.d.ts +100 -0
- package/node-server/dist/terminal/pty-manager.js +246 -0
- package/node-server/dist/terminal/server.d.ts +19 -0
- package/node-server/dist/terminal/server.js +254 -0
- package/node-server/dist/terminal/types.d.ts +50 -0
- package/node-server/dist/terminal/types.js +9 -0
- package/node-server/package.json +45 -0
- package/package.json +99 -0
- package/scripts/postinstall.cjs +26 -0
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Headless Undo/Redo Manager
|
|
4
|
+
*
|
|
5
|
+
* Manages undo/redo state for notebooks when operated via the headless backend (MCP).
|
|
6
|
+
* This mirrors the frontend UndoRedoManager but uses NebulaCell types.
|
|
7
|
+
*
|
|
8
|
+
* Note: This is a simplified version of lib/undoRedoCore.ts adapted for the backend.
|
|
9
|
+
* The core logic is intentionally similar to maintain feature parity.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.HeadlessUndoRedoManager = void 0;
|
|
13
|
+
exports.getUndoRedoManager = getUndoRedoManager;
|
|
14
|
+
const uuid_1 = require("uuid");
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// PURE FUNCTIONS
|
|
17
|
+
// ============================================================================
|
|
18
|
+
function cloneCell(cell) {
|
|
19
|
+
return {
|
|
20
|
+
...cell,
|
|
21
|
+
outputs: (cell.outputs || []).map(output => ({ ...output }))
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function stripCellOutputs(cell) {
|
|
25
|
+
return {
|
|
26
|
+
...cell,
|
|
27
|
+
outputs: [],
|
|
28
|
+
isExecuting: false
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function applyOperation(cells, op) {
|
|
32
|
+
switch (op.type) {
|
|
33
|
+
case 'insertCell': {
|
|
34
|
+
const newCells = [...cells];
|
|
35
|
+
newCells.splice(op.index, 0, cloneCell(op.cell));
|
|
36
|
+
return newCells;
|
|
37
|
+
}
|
|
38
|
+
case 'deleteCell': {
|
|
39
|
+
return cells.filter((_, i) => i !== op.index);
|
|
40
|
+
}
|
|
41
|
+
case 'moveCell': {
|
|
42
|
+
const newCells = [...cells];
|
|
43
|
+
const [moved] = newCells.splice(op.fromIndex, 1);
|
|
44
|
+
newCells.splice(op.toIndex, 0, moved);
|
|
45
|
+
return newCells;
|
|
46
|
+
}
|
|
47
|
+
case 'updateContent': {
|
|
48
|
+
return cells.map(c => c.id === op.cellId ? { ...c, content: op.newContent } : c);
|
|
49
|
+
}
|
|
50
|
+
case 'updateMetadata': {
|
|
51
|
+
return cells.map(c => {
|
|
52
|
+
if (c.id !== op.cellId)
|
|
53
|
+
return c;
|
|
54
|
+
const updated = { ...c };
|
|
55
|
+
for (const [key, change] of Object.entries(op.changes)) {
|
|
56
|
+
updated[key] = change.new;
|
|
57
|
+
}
|
|
58
|
+
return updated;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
case 'batch': {
|
|
62
|
+
return op.operations.reduce((acc, subOp) => applyOperation(acc, subOp), cells);
|
|
63
|
+
}
|
|
64
|
+
default:
|
|
65
|
+
return cells;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function reverseOperation(op) {
|
|
69
|
+
switch (op.type) {
|
|
70
|
+
case 'insertCell':
|
|
71
|
+
return { type: 'deleteCell', index: op.index, cell: op.cell };
|
|
72
|
+
case 'deleteCell':
|
|
73
|
+
return { type: 'insertCell', index: op.index, cell: op.cell };
|
|
74
|
+
case 'moveCell':
|
|
75
|
+
return { type: 'moveCell', fromIndex: op.toIndex, toIndex: op.fromIndex };
|
|
76
|
+
case 'updateContent':
|
|
77
|
+
return {
|
|
78
|
+
type: 'updateContent',
|
|
79
|
+
cellId: op.cellId,
|
|
80
|
+
oldContent: op.newContent,
|
|
81
|
+
newContent: op.oldContent
|
|
82
|
+
};
|
|
83
|
+
case 'updateMetadata': {
|
|
84
|
+
const reversed = {};
|
|
85
|
+
for (const [key, change] of Object.entries(op.changes)) {
|
|
86
|
+
reversed[key] = { old: change.new, new: change.old };
|
|
87
|
+
}
|
|
88
|
+
return { type: 'updateMetadata', cellId: op.cellId, changes: reversed };
|
|
89
|
+
}
|
|
90
|
+
case 'batch':
|
|
91
|
+
return {
|
|
92
|
+
type: 'batch',
|
|
93
|
+
operations: op.operations.map(reverseOperation).reverse()
|
|
94
|
+
};
|
|
95
|
+
default:
|
|
96
|
+
return op;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function getAffectedCellIds(op, cells) {
|
|
100
|
+
switch (op.type) {
|
|
101
|
+
case 'insertCell':
|
|
102
|
+
case 'deleteCell':
|
|
103
|
+
return [op.cell.id];
|
|
104
|
+
case 'moveCell':
|
|
105
|
+
if (cells && op.toIndex >= 0 && op.toIndex < cells.length) {
|
|
106
|
+
return [cells[op.toIndex].id];
|
|
107
|
+
}
|
|
108
|
+
return [];
|
|
109
|
+
case 'updateContent':
|
|
110
|
+
case 'updateMetadata':
|
|
111
|
+
return [op.cellId];
|
|
112
|
+
case 'batch': {
|
|
113
|
+
const ids = new Set();
|
|
114
|
+
for (const subOp of op.operations) {
|
|
115
|
+
for (const id of getAffectedCellIds(subOp, cells)) {
|
|
116
|
+
ids.add(id);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return Array.from(ids);
|
|
120
|
+
}
|
|
121
|
+
default:
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// ============================================================================
|
|
126
|
+
// HEADLESS UNDO/REDO MANAGER
|
|
127
|
+
// ============================================================================
|
|
128
|
+
/**
|
|
129
|
+
* Manages undo/redo state for multiple notebooks in the headless backend.
|
|
130
|
+
*/
|
|
131
|
+
class HeadlessUndoRedoManager {
|
|
132
|
+
states = new Map();
|
|
133
|
+
fsService;
|
|
134
|
+
constructor(fsService) {
|
|
135
|
+
this.fsService = fsService;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Get or initialize undo state for a notebook.
|
|
139
|
+
*/
|
|
140
|
+
getState(notebookPath, cells) {
|
|
141
|
+
if (!this.states.has(notebookPath)) {
|
|
142
|
+
// Try to load existing history
|
|
143
|
+
let loadedHistory = [];
|
|
144
|
+
try {
|
|
145
|
+
loadedHistory = this.fsService.loadHistory(notebookPath);
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
console.warn(`[UndoRedoManager] Failed to load history for ${notebookPath}:`, err);
|
|
149
|
+
}
|
|
150
|
+
// Initialize state
|
|
151
|
+
const state = {
|
|
152
|
+
undoStack: [],
|
|
153
|
+
redoStack: [],
|
|
154
|
+
fullHistory: loadedHistory.length > 0 ? loadedHistory : [{
|
|
155
|
+
type: 'snapshot',
|
|
156
|
+
cells: cells.map(stripCellOutputs),
|
|
157
|
+
timestamp: Date.now()
|
|
158
|
+
}],
|
|
159
|
+
lastContent: new Map(cells.map(c => [c.id, c.content]))
|
|
160
|
+
};
|
|
161
|
+
// Rebuild undo stack from history
|
|
162
|
+
if (loadedHistory.length > 0) {
|
|
163
|
+
state.undoStack = this.rebuildUndoStack(loadedHistory);
|
|
164
|
+
}
|
|
165
|
+
this.states.set(notebookPath, state);
|
|
166
|
+
}
|
|
167
|
+
return this.states.get(notebookPath);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Rebuild undo stack from history.
|
|
171
|
+
*/
|
|
172
|
+
rebuildUndoStack(history) {
|
|
173
|
+
const undoneIds = new Set();
|
|
174
|
+
for (const op of history) {
|
|
175
|
+
if (op.isUndo && op.undoesOperationId) {
|
|
176
|
+
undoneIds.add(op.undoesOperationId);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const undoableOps = [];
|
|
180
|
+
for (const op of history) {
|
|
181
|
+
if (op.isUndo)
|
|
182
|
+
continue;
|
|
183
|
+
if (op.operationId && undoneIds.has(op.operationId))
|
|
184
|
+
continue;
|
|
185
|
+
if (op.type === 'insertCell' || op.type === 'deleteCell' ||
|
|
186
|
+
op.type === 'moveCell' || op.type === 'updateContent' ||
|
|
187
|
+
op.type === 'updateMetadata' || op.type === 'batch') {
|
|
188
|
+
const { timestamp, operationId, isUndo, undoesOperationId, ...operation } = op;
|
|
189
|
+
undoableOps.push(operation);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return undoableOps;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Record an operation (called by headless handler after executing an operation).
|
|
196
|
+
*/
|
|
197
|
+
recordOperation(notebookPath, cells, op) {
|
|
198
|
+
const state = this.getState(notebookPath, cells);
|
|
199
|
+
// Clear redo stack (can't redo after new operation)
|
|
200
|
+
if (state.redoStack.length > 0) {
|
|
201
|
+
// Record the undos that would have been in redo stack
|
|
202
|
+
for (const redoOp of state.redoStack) {
|
|
203
|
+
const reversedOp = reverseOperation(redoOp);
|
|
204
|
+
const timestampedOp = {
|
|
205
|
+
...reversedOp,
|
|
206
|
+
timestamp: Date.now(),
|
|
207
|
+
operationId: (0, uuid_1.v4)(),
|
|
208
|
+
isUndo: true,
|
|
209
|
+
undoesOperationId: redoOp.operationId,
|
|
210
|
+
};
|
|
211
|
+
state.fullHistory.push(timestampedOp);
|
|
212
|
+
}
|
|
213
|
+
state.redoStack = [];
|
|
214
|
+
}
|
|
215
|
+
// Add operation to history
|
|
216
|
+
const operationId = (0, uuid_1.v4)();
|
|
217
|
+
const timestampedOp = {
|
|
218
|
+
...op,
|
|
219
|
+
timestamp: Date.now(),
|
|
220
|
+
operationId,
|
|
221
|
+
};
|
|
222
|
+
state.fullHistory.push(timestampedOp);
|
|
223
|
+
// Add to undo stack
|
|
224
|
+
const opWithId = { ...op, operationId };
|
|
225
|
+
state.undoStack.push(opWithId);
|
|
226
|
+
// Update content tracking
|
|
227
|
+
if (op.type === 'updateContent') {
|
|
228
|
+
state.lastContent.set(op.cellId, op.newContent);
|
|
229
|
+
}
|
|
230
|
+
else if (op.type === 'insertCell') {
|
|
231
|
+
state.lastContent.set(op.cell.id, op.cell.content);
|
|
232
|
+
}
|
|
233
|
+
else if (op.type === 'deleteCell') {
|
|
234
|
+
state.lastContent.delete(op.cell.id);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Record a non-undoable log operation (history only).
|
|
239
|
+
*/
|
|
240
|
+
recordLogOperation(notebookPath, cells, op) {
|
|
241
|
+
const state = this.getState(notebookPath, cells);
|
|
242
|
+
const timestampedOp = {
|
|
243
|
+
...op,
|
|
244
|
+
timestamp: Date.now(),
|
|
245
|
+
operationId: (0, uuid_1.v4)(),
|
|
246
|
+
};
|
|
247
|
+
state.fullHistory.push(timestampedOp);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Undo the last operation.
|
|
251
|
+
*/
|
|
252
|
+
undo(notebookPath, cells) {
|
|
253
|
+
const state = this.getState(notebookPath, cells);
|
|
254
|
+
if (state.undoStack.length === 0) {
|
|
255
|
+
return {
|
|
256
|
+
cells,
|
|
257
|
+
result: { success: false, affectedCellIds: [], operationType: '', error: 'Nothing to undo' }
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
const op = state.undoStack.pop();
|
|
261
|
+
const reversedOp = reverseOperation(op);
|
|
262
|
+
// Apply the reversed operation
|
|
263
|
+
const newCells = applyOperation(cells, reversedOp);
|
|
264
|
+
// Push to redo stack
|
|
265
|
+
state.redoStack.push(op);
|
|
266
|
+
// Update content tracking
|
|
267
|
+
this.updateContentTrackingAfterUndo(state, op, newCells);
|
|
268
|
+
return {
|
|
269
|
+
cells: newCells,
|
|
270
|
+
result: {
|
|
271
|
+
success: true,
|
|
272
|
+
affectedCellIds: getAffectedCellIds(reversedOp, newCells),
|
|
273
|
+
operationType: op.type
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Redo the last undone operation.
|
|
279
|
+
*/
|
|
280
|
+
redo(notebookPath, cells) {
|
|
281
|
+
const state = this.getState(notebookPath, cells);
|
|
282
|
+
if (state.redoStack.length === 0) {
|
|
283
|
+
return {
|
|
284
|
+
cells,
|
|
285
|
+
result: { success: false, affectedCellIds: [], operationType: '', error: 'Nothing to redo' }
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
const op = state.redoStack.pop();
|
|
289
|
+
// Apply the operation
|
|
290
|
+
const newCells = applyOperation(cells, op);
|
|
291
|
+
// Push to undo stack
|
|
292
|
+
state.undoStack.push(op);
|
|
293
|
+
// Update content tracking
|
|
294
|
+
this.updateContentTrackingAfterRedo(state, op, newCells);
|
|
295
|
+
return {
|
|
296
|
+
cells: newCells,
|
|
297
|
+
result: {
|
|
298
|
+
success: true,
|
|
299
|
+
affectedCellIds: getAffectedCellIds(op, newCells),
|
|
300
|
+
operationType: op.type
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
updateContentTrackingAfterUndo(state, op, cells) {
|
|
305
|
+
if (op.type === 'updateContent') {
|
|
306
|
+
state.lastContent.set(op.cellId, op.oldContent);
|
|
307
|
+
}
|
|
308
|
+
else if (op.type === 'insertCell') {
|
|
309
|
+
state.lastContent.delete(op.cell.id);
|
|
310
|
+
}
|
|
311
|
+
else if (op.type === 'deleteCell') {
|
|
312
|
+
state.lastContent.set(op.cell.id, op.cell.content);
|
|
313
|
+
}
|
|
314
|
+
else if (op.type === 'batch') {
|
|
315
|
+
for (let i = op.operations.length - 1; i >= 0; i--) {
|
|
316
|
+
this.updateContentTrackingAfterUndo(state, op.operations[i], cells);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
updateContentTrackingAfterRedo(state, op, cells) {
|
|
321
|
+
if (op.type === 'updateContent') {
|
|
322
|
+
state.lastContent.set(op.cellId, op.newContent);
|
|
323
|
+
}
|
|
324
|
+
else if (op.type === 'insertCell') {
|
|
325
|
+
state.lastContent.set(op.cell.id, op.cell.content);
|
|
326
|
+
}
|
|
327
|
+
else if (op.type === 'deleteCell') {
|
|
328
|
+
state.lastContent.delete(op.cell.id);
|
|
329
|
+
}
|
|
330
|
+
else if (op.type === 'batch') {
|
|
331
|
+
for (const subOp of op.operations) {
|
|
332
|
+
this.updateContentTrackingAfterRedo(state, subOp, cells);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Check if undo is available.
|
|
338
|
+
*/
|
|
339
|
+
canUndo(notebookPath, cells) {
|
|
340
|
+
const state = this.getState(notebookPath, cells);
|
|
341
|
+
return state.undoStack.length > 0;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Check if redo is available.
|
|
345
|
+
*/
|
|
346
|
+
canRedo(notebookPath, cells) {
|
|
347
|
+
const state = this.getState(notebookPath, cells);
|
|
348
|
+
return state.redoStack.length > 0;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Get history for a notebook.
|
|
352
|
+
*/
|
|
353
|
+
getHistory(notebookPath, cells) {
|
|
354
|
+
const state = this.getState(notebookPath, cells);
|
|
355
|
+
return state.fullHistory;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Clear state for a notebook (e.g., when closing).
|
|
359
|
+
*/
|
|
360
|
+
clearState(notebookPath) {
|
|
361
|
+
this.states.delete(notebookPath);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Get updates since a timestamp (for agent awareness).
|
|
365
|
+
* Returns human-readable summaries of operations made between agent sessions.
|
|
366
|
+
* Includes both edits and non-undoable events (no source filtering).
|
|
367
|
+
*/
|
|
368
|
+
getUpdatesSince(notebookPath, cells, sinceTimestamp) {
|
|
369
|
+
const state = this.getState(notebookPath, cells);
|
|
370
|
+
const summaries = [];
|
|
371
|
+
const describeEvent = (event, timestamp) => {
|
|
372
|
+
const cellId = event.target?.cellId;
|
|
373
|
+
const cellIndex = event.target?.cellIndex;
|
|
374
|
+
const data = event.data ? { ...event.data } : undefined;
|
|
375
|
+
if (data && event.name === 'runCellComplete' && 'output' in data) {
|
|
376
|
+
delete data.output;
|
|
377
|
+
}
|
|
378
|
+
let description = '';
|
|
379
|
+
switch (event.name) {
|
|
380
|
+
case 'runCell': {
|
|
381
|
+
description = `Ran cell${cellIndex !== undefined ? ` #${cellIndex + 1}` : ''}`;
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
case 'runAllCells': {
|
|
385
|
+
const count = typeof data?.cellCount === 'number'
|
|
386
|
+
? data.cellCount
|
|
387
|
+
: (Array.isArray(data?.cellIds) ? data.cellIds.length : undefined);
|
|
388
|
+
description = `Ran all cells${count !== undefined ? ` (${count})` : ''}`;
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
case 'runCellComplete': {
|
|
392
|
+
const durationMs = typeof data?.durationMs === 'number' ? data.durationMs : undefined;
|
|
393
|
+
const success = typeof data?.success === 'boolean' ? data.success : undefined;
|
|
394
|
+
const status = success === undefined ? 'complete' : (success ? 'success' : 'failed');
|
|
395
|
+
description = `Run complete${cellIndex !== undefined ? ` for cell #${cellIndex + 1}` : ''}${durationMs !== undefined ? ` (${durationMs}ms)` : ''} • ${status}`;
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
case 'interruptKernel':
|
|
399
|
+
description = 'Kernel interrupted';
|
|
400
|
+
break;
|
|
401
|
+
case 'restartKernel':
|
|
402
|
+
description = 'Kernel restarted';
|
|
403
|
+
break;
|
|
404
|
+
case 'startKernel': {
|
|
405
|
+
const kernelName = typeof data?.kernelName === 'string' ? data.kernelName : undefined;
|
|
406
|
+
description = `Kernel started${kernelName ? ` (${kernelName})` : ''}`;
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
409
|
+
case 'shutdownKernel':
|
|
410
|
+
description = 'Kernel shutdown';
|
|
411
|
+
break;
|
|
412
|
+
default:
|
|
413
|
+
description = `Event: ${event.name}`;
|
|
414
|
+
}
|
|
415
|
+
summaries.push({
|
|
416
|
+
kind: 'event',
|
|
417
|
+
type: event.name,
|
|
418
|
+
category: event.category,
|
|
419
|
+
name: event.name,
|
|
420
|
+
cellId,
|
|
421
|
+
cellIndex,
|
|
422
|
+
timestamp,
|
|
423
|
+
description,
|
|
424
|
+
source: event.source,
|
|
425
|
+
runId: event.runId,
|
|
426
|
+
data,
|
|
427
|
+
});
|
|
428
|
+
};
|
|
429
|
+
const toEvent = (op) => {
|
|
430
|
+
if (op.type === 'event') {
|
|
431
|
+
return {
|
|
432
|
+
category: op.category,
|
|
433
|
+
name: op.name,
|
|
434
|
+
target: op.target,
|
|
435
|
+
data: op.data,
|
|
436
|
+
runId: op.runId,
|
|
437
|
+
source: op.source,
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
switch (op.type) {
|
|
441
|
+
case 'runCell':
|
|
442
|
+
return {
|
|
443
|
+
category: 'execution',
|
|
444
|
+
name: 'runCell',
|
|
445
|
+
target: { cellId: op.cellId, cellIndex: op.cellIndex },
|
|
446
|
+
runId: op.runId,
|
|
447
|
+
};
|
|
448
|
+
case 'runAllCells':
|
|
449
|
+
return {
|
|
450
|
+
category: 'execution',
|
|
451
|
+
name: 'runAllCells',
|
|
452
|
+
data: {
|
|
453
|
+
cellCount: op.cellCount,
|
|
454
|
+
cellIds: op.cellIds,
|
|
455
|
+
},
|
|
456
|
+
};
|
|
457
|
+
case 'runCellComplete':
|
|
458
|
+
return {
|
|
459
|
+
category: 'execution',
|
|
460
|
+
name: 'runCellComplete',
|
|
461
|
+
target: { cellId: op.cellId, cellIndex: op.cellIndex },
|
|
462
|
+
data: {
|
|
463
|
+
durationMs: op.durationMs,
|
|
464
|
+
success: op.success,
|
|
465
|
+
output: op.output,
|
|
466
|
+
},
|
|
467
|
+
runId: op.runId,
|
|
468
|
+
};
|
|
469
|
+
case 'interruptKernel':
|
|
470
|
+
case 'restartKernel':
|
|
471
|
+
return {
|
|
472
|
+
category: 'kernel',
|
|
473
|
+
name: op.type,
|
|
474
|
+
};
|
|
475
|
+
default:
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
for (const op of state.fullHistory) {
|
|
480
|
+
if (op.timestamp <= sinceTimestamp)
|
|
481
|
+
continue;
|
|
482
|
+
if (op.isUndo)
|
|
483
|
+
continue;
|
|
484
|
+
if (op.type === 'snapshot')
|
|
485
|
+
continue;
|
|
486
|
+
const event = toEvent(op);
|
|
487
|
+
if (event) {
|
|
488
|
+
describeEvent(event, op.timestamp);
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
let description = '';
|
|
492
|
+
let cellId;
|
|
493
|
+
let cellIndex;
|
|
494
|
+
switch (op.type) {
|
|
495
|
+
case 'insertCell':
|
|
496
|
+
cellId = op.cell.id;
|
|
497
|
+
cellIndex = op.index;
|
|
498
|
+
description = `Inserted ${op.cell.type} cell at #${op.index + 1}`;
|
|
499
|
+
break;
|
|
500
|
+
case 'deleteCell':
|
|
501
|
+
cellId = op.cell.id;
|
|
502
|
+
cellIndex = op.index;
|
|
503
|
+
description = `Deleted cell #${op.index + 1}`;
|
|
504
|
+
break;
|
|
505
|
+
case 'moveCell':
|
|
506
|
+
if (op.toIndex >= 0 && op.toIndex < cells.length) {
|
|
507
|
+
cellId = cells[op.toIndex].id;
|
|
508
|
+
}
|
|
509
|
+
description = `Moved cell from #${op.fromIndex + 1} to #${op.toIndex + 1}`;
|
|
510
|
+
break;
|
|
511
|
+
case 'updateContent': {
|
|
512
|
+
cellId = op.cellId;
|
|
513
|
+
cellIndex = cells.findIndex(c => c.id === op.cellId);
|
|
514
|
+
if (cellIndex === -1)
|
|
515
|
+
cellIndex = undefined;
|
|
516
|
+
const preview = op.newContent.slice(0, 50).replace(/\n/g, ' ');
|
|
517
|
+
description = `Edited cell${cellIndex !== undefined ? ` #${cellIndex + 1}` : ''}: \"${preview}${preview.length >= 50 ? '...' : ''}\"`;
|
|
518
|
+
break;
|
|
519
|
+
}
|
|
520
|
+
case 'updateMetadata': {
|
|
521
|
+
cellId = op.cellId;
|
|
522
|
+
cellIndex = cells.findIndex(c => c.id === op.cellId);
|
|
523
|
+
if (cellIndex === -1)
|
|
524
|
+
cellIndex = undefined;
|
|
525
|
+
const changes = Object.keys(op.changes).join(', ');
|
|
526
|
+
description = `Changed ${changes} on cell${cellIndex !== undefined ? ` #${cellIndex + 1}` : ''}`;
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
case 'batch':
|
|
530
|
+
description = `Batch operation (${op.operations.length} changes)`;
|
|
531
|
+
break;
|
|
532
|
+
default:
|
|
533
|
+
description = `Operation: ${op.type}`;
|
|
534
|
+
}
|
|
535
|
+
summaries.push({
|
|
536
|
+
kind: 'edit',
|
|
537
|
+
type: op.type,
|
|
538
|
+
cellId,
|
|
539
|
+
cellIndex,
|
|
540
|
+
timestamp: op.timestamp,
|
|
541
|
+
description,
|
|
542
|
+
source: op.source,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
return summaries;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
exports.HeadlessUndoRedoManager = HeadlessUndoRedoManager;
|
|
549
|
+
// Manager instances per FilesystemService to avoid cross-root leakage in tests/multi-root setups
|
|
550
|
+
const managers = new WeakMap();
|
|
551
|
+
function getUndoRedoManager(fsService) {
|
|
552
|
+
let manager = managers.get(fsService);
|
|
553
|
+
if (!manager) {
|
|
554
|
+
manager = new HeadlessUndoRedoManager(fsService);
|
|
555
|
+
managers.set(fsService, manager);
|
|
556
|
+
}
|
|
557
|
+
return manager;
|
|
558
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { JsonValue, MimeBundle, OutputType } from '../fs/types';
|
|
2
|
+
export interface NormalizedDisplayOutput {
|
|
3
|
+
type: OutputType;
|
|
4
|
+
content: string;
|
|
5
|
+
mimeBundle: MimeBundle;
|
|
6
|
+
metadata?: Record<string, JsonValue>;
|
|
7
|
+
preferredMimeType: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function stripAutoplayFromHtml(html: string): string;
|
|
10
|
+
export declare function normalizeMimeValue(mimeType: string, value: unknown): JsonValue | null;
|
|
11
|
+
export declare function normalizeMimeBundle(data: Record<string, unknown>): MimeBundle;
|
|
12
|
+
export declare function pickPreferredMimeType(bundle: MimeBundle): string | null;
|
|
13
|
+
export declare function buildDisplayOutput(data: Record<string, unknown>, metadata?: Record<string, unknown>): NormalizedDisplayOutput | null;
|
|
14
|
+
export declare function convertMimeBundleToJupyter(bundle: MimeBundle): Record<string, JsonValue>;
|