mrmd-editor 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -109,6 +109,8 @@ function createSegment(type, context) {
|
|
|
109
109
|
return createSyncSegment(context);
|
|
110
110
|
case 'runtime':
|
|
111
111
|
return createRuntimeSegment(context);
|
|
112
|
+
case 'runtimes':
|
|
113
|
+
return createRuntimesSegment(context);
|
|
112
114
|
case 'ai':
|
|
113
115
|
return createAiSegment(context);
|
|
114
116
|
case 'theme':
|
|
@@ -1058,6 +1060,241 @@ function createThemeSegment({ editorRef, shellState, handlers, onCleanup }) {
|
|
|
1058
1060
|
return segment;
|
|
1059
1061
|
}
|
|
1060
1062
|
|
|
1063
|
+
// =============================================================================
|
|
1064
|
+
// RUNTIMES SEGMENT (Project-wide runtime management)
|
|
1065
|
+
// =============================================================================
|
|
1066
|
+
|
|
1067
|
+
function createRuntimesSegment({ shellState, orchestratorClient, handlers, onCleanup }) {
|
|
1068
|
+
const segment = document.createElement('div');
|
|
1069
|
+
segment.className = 'mrmd-statusbar__segment mrmd-statusbar__segment--runtimes';
|
|
1070
|
+
segment.setAttribute('data-segment', 'runtimes');
|
|
1071
|
+
|
|
1072
|
+
let currentMenu = null;
|
|
1073
|
+
let cachedRuntimes = null;
|
|
1074
|
+
let lastFetchTime = 0;
|
|
1075
|
+
const CACHE_TTL = 3000; // 3 seconds
|
|
1076
|
+
|
|
1077
|
+
async function fetchRuntimes() {
|
|
1078
|
+
const now = Date.now();
|
|
1079
|
+
if (now - lastFetchTime < CACHE_TTL && cachedRuntimes) {
|
|
1080
|
+
return cachedRuntimes;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
try {
|
|
1084
|
+
cachedRuntimes = await orchestratorClient.listRuntimes();
|
|
1085
|
+
lastFetchTime = now;
|
|
1086
|
+
return cachedRuntimes;
|
|
1087
|
+
} catch (error) {
|
|
1088
|
+
console.error('Failed to list runtimes:', error);
|
|
1089
|
+
return cachedRuntimes || { shared: null, dedicated: [], sessions: [], project: {} };
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
function render() {
|
|
1094
|
+
const python = shellState.get('runtimes.python');
|
|
1095
|
+
const project = shellState.get('project') || {};
|
|
1096
|
+
|
|
1097
|
+
// Count active runtimes
|
|
1098
|
+
let runtimeCount = 0;
|
|
1099
|
+
if (python?.running || python?.status === 'ready') runtimeCount++;
|
|
1100
|
+
|
|
1101
|
+
const sessions = shellState.getSessions();
|
|
1102
|
+
const dedicatedCount = sessions.filter(s => s.info?.dedicated).length;
|
|
1103
|
+
runtimeCount += dedicatedCount;
|
|
1104
|
+
|
|
1105
|
+
// Project name
|
|
1106
|
+
const projectName = project.name || 'mrmd';
|
|
1107
|
+
|
|
1108
|
+
// Status dot
|
|
1109
|
+
const hasActiveRuntime = runtimeCount > 0;
|
|
1110
|
+
const dotClass = hasActiveRuntime ? 'connected' : 'disconnected';
|
|
1111
|
+
|
|
1112
|
+
segment.innerHTML = `
|
|
1113
|
+
<span class="mrmd-statusbar__dot mrmd-statusbar__dot--${dotClass}"></span>
|
|
1114
|
+
<span class="mrmd-statusbar__icon">⚡</span>
|
|
1115
|
+
<span class="mrmd-statusbar__label">${projectName}</span>
|
|
1116
|
+
${runtimeCount > 0 ? `<span class="mrmd-statusbar__badge">${runtimeCount} runtime${runtimeCount > 1 ? 's' : ''}</span>` : ''}
|
|
1117
|
+
<span class="mrmd-statusbar__chevron">▾</span>
|
|
1118
|
+
`;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
async function openMenu() {
|
|
1122
|
+
if (currentMenu) {
|
|
1123
|
+
currentMenu.close();
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
const runtimes = await fetchRuntimes();
|
|
1128
|
+
const items = [];
|
|
1129
|
+
|
|
1130
|
+
// Project section
|
|
1131
|
+
const project = runtimes.project || {};
|
|
1132
|
+
items.push({
|
|
1133
|
+
type: 'header',
|
|
1134
|
+
label: 'Project',
|
|
1135
|
+
});
|
|
1136
|
+
items.push({
|
|
1137
|
+
type: 'info',
|
|
1138
|
+
label: 'Name',
|
|
1139
|
+
value: project.name || 'unknown',
|
|
1140
|
+
});
|
|
1141
|
+
items.push({
|
|
1142
|
+
type: 'info',
|
|
1143
|
+
label: 'Root',
|
|
1144
|
+
value: shortenPath(project.root, 35),
|
|
1145
|
+
});
|
|
1146
|
+
if (project.venv) {
|
|
1147
|
+
items.push({
|
|
1148
|
+
type: 'info',
|
|
1149
|
+
label: 'Venv',
|
|
1150
|
+
value: shortenPath(project.venv, 35),
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// Shared Runtime section
|
|
1155
|
+
items.push({ type: 'divider' });
|
|
1156
|
+
items.push({
|
|
1157
|
+
type: 'header',
|
|
1158
|
+
label: 'Shared Runtime',
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1161
|
+
if (runtimes.shared && runtimes.shared.alive) {
|
|
1162
|
+
items.push({
|
|
1163
|
+
type: 'info',
|
|
1164
|
+
label: 'Status',
|
|
1165
|
+
value: '● Running',
|
|
1166
|
+
});
|
|
1167
|
+
items.push({
|
|
1168
|
+
type: 'info',
|
|
1169
|
+
label: 'PID',
|
|
1170
|
+
value: String(runtimes.shared.pid || 'unknown'),
|
|
1171
|
+
});
|
|
1172
|
+
items.push({
|
|
1173
|
+
type: 'info',
|
|
1174
|
+
label: 'Port',
|
|
1175
|
+
value: String(runtimes.shared.port || 'unknown'),
|
|
1176
|
+
});
|
|
1177
|
+
if (runtimes.shared.venv) {
|
|
1178
|
+
items.push({
|
|
1179
|
+
type: 'info',
|
|
1180
|
+
label: 'Venv',
|
|
1181
|
+
value: shortenPath(runtimes.shared.venv, 30),
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
items.push({
|
|
1185
|
+
icon: '💀',
|
|
1186
|
+
label: 'Kill shared runtime',
|
|
1187
|
+
description: 'Release memory, restarts on next exec',
|
|
1188
|
+
onClick: async () => {
|
|
1189
|
+
await handlers.onKillRuntime?.('shared');
|
|
1190
|
+
// Refresh after a moment
|
|
1191
|
+
setTimeout(render, 500);
|
|
1192
|
+
},
|
|
1193
|
+
});
|
|
1194
|
+
} else {
|
|
1195
|
+
items.push({
|
|
1196
|
+
type: 'info',
|
|
1197
|
+
label: 'Status',
|
|
1198
|
+
value: '○ Not running',
|
|
1199
|
+
});
|
|
1200
|
+
items.push({
|
|
1201
|
+
type: 'info',
|
|
1202
|
+
label: '',
|
|
1203
|
+
value: 'Starts on first code execution',
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// Dedicated Runtimes section
|
|
1208
|
+
if (runtimes.dedicated && runtimes.dedicated.length > 0) {
|
|
1209
|
+
items.push({ type: 'divider' });
|
|
1210
|
+
items.push({
|
|
1211
|
+
type: 'header',
|
|
1212
|
+
label: `Dedicated Runtimes (${runtimes.dedicated.length})`,
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
for (const rt of runtimes.dedicated) {
|
|
1216
|
+
const statusIcon = rt.alive ? '●' : '○';
|
|
1217
|
+
const statusText = rt.alive ? 'running' : 'stopped';
|
|
1218
|
+
items.push({
|
|
1219
|
+
type: 'info',
|
|
1220
|
+
label: rt.doc || rt.id,
|
|
1221
|
+
value: `${statusIcon} ${statusText} (port ${rt.port || '?'})`,
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
items.push({
|
|
1226
|
+
icon: '🗑️',
|
|
1227
|
+
label: 'Kill all dedicated runtimes',
|
|
1228
|
+
onClick: async () => {
|
|
1229
|
+
for (const rt of runtimes.dedicated) {
|
|
1230
|
+
await handlers.onKillRuntime?.(rt.doc || rt.id);
|
|
1231
|
+
}
|
|
1232
|
+
setTimeout(render, 500);
|
|
1233
|
+
},
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// Sessions section
|
|
1238
|
+
if (runtimes.sessions && runtimes.sessions.length > 0) {
|
|
1239
|
+
items.push({ type: 'divider' });
|
|
1240
|
+
items.push({
|
|
1241
|
+
type: 'header',
|
|
1242
|
+
label: `Active Sessions (${runtimes.sessions.length})`,
|
|
1243
|
+
});
|
|
1244
|
+
|
|
1245
|
+
for (const session of runtimes.sessions) {
|
|
1246
|
+
if (!session) continue;
|
|
1247
|
+
const runtimeType = session.runtimes?.python?.dedicated ? 'dedicated' : 'shared';
|
|
1248
|
+
const port = session.runtimes?.python?.port;
|
|
1249
|
+
items.push({
|
|
1250
|
+
type: 'info',
|
|
1251
|
+
label: session.doc,
|
|
1252
|
+
value: `${runtimeType}${port ? ` (port ${port})` : ''}`,
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// Actions section
|
|
1258
|
+
items.push({ type: 'divider' });
|
|
1259
|
+
items.push({
|
|
1260
|
+
icon: '🔄',
|
|
1261
|
+
label: 'Refresh',
|
|
1262
|
+
onClick: async () => {
|
|
1263
|
+
cachedRuntimes = null;
|
|
1264
|
+
lastFetchTime = 0;
|
|
1265
|
+
await shellState.refresh();
|
|
1266
|
+
render();
|
|
1267
|
+
},
|
|
1268
|
+
});
|
|
1269
|
+
|
|
1270
|
+
currentMenu = createMenu({
|
|
1271
|
+
items,
|
|
1272
|
+
anchor: segment,
|
|
1273
|
+
position: 'bottom-right',
|
|
1274
|
+
onClose: () => { currentMenu = null; },
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
segment.addEventListener('click', openMenu);
|
|
1279
|
+
|
|
1280
|
+
// Subscribe to state changes
|
|
1281
|
+
const unsubscribe1 = shellState.onPath('runtimes', render);
|
|
1282
|
+
const unsubscribe2 = shellState.onPath('project', render);
|
|
1283
|
+
const unsubscribe3 = shellState.onPath('orchestrator.services', render);
|
|
1284
|
+
onCleanup(unsubscribe1);
|
|
1285
|
+
onCleanup(unsubscribe2);
|
|
1286
|
+
onCleanup(unsubscribe3);
|
|
1287
|
+
onCleanup(() => currentMenu?.close());
|
|
1288
|
+
|
|
1289
|
+
// Initial fetch of project info
|
|
1290
|
+
orchestratorClient.getProject().then(project => {
|
|
1291
|
+
shellState._set('project', project);
|
|
1292
|
+
}).catch(() => {});
|
|
1293
|
+
|
|
1294
|
+
render();
|
|
1295
|
+
return segment;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1061
1298
|
// =============================================================================
|
|
1062
1299
|
// HELPERS
|
|
1063
1300
|
// =============================================================================
|
|
@@ -142,6 +142,62 @@ export async function createStudio(target, options = {}) {
|
|
|
142
142
|
return () => eventHandlers.get(event).delete(handler);
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Detect if cursor is inside a code block and return block info
|
|
147
|
+
* @param {EditorView} view
|
|
148
|
+
* @returns {{language: string, code: string, start: number, end: number}|null}
|
|
149
|
+
*/
|
|
150
|
+
function detectCodeBlockAtCursor(view) {
|
|
151
|
+
const content = view.state.doc.toString();
|
|
152
|
+
const pos = view.state.selection.main.head;
|
|
153
|
+
|
|
154
|
+
// Parse code blocks from content
|
|
155
|
+
const lines = content.split('\n');
|
|
156
|
+
let inBlock = false;
|
|
157
|
+
let blockStart = 0;
|
|
158
|
+
let blockLanguage = '';
|
|
159
|
+
let codeStart = 0;
|
|
160
|
+
let charOffset = 0;
|
|
161
|
+
|
|
162
|
+
for (let i = 0; i < lines.length; i++) {
|
|
163
|
+
const line = lines[i];
|
|
164
|
+
const lineStart = charOffset;
|
|
165
|
+
|
|
166
|
+
if (!inBlock) {
|
|
167
|
+
const match = line.match(/^(`{3,})(\w*)/);
|
|
168
|
+
if (match) {
|
|
169
|
+
inBlock = true;
|
|
170
|
+
blockStart = lineStart;
|
|
171
|
+
blockLanguage = match[2].toLowerCase();
|
|
172
|
+
codeStart = lineStart + line.length + 1;
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
if (line.match(/^`{3,}\s*$/)) {
|
|
176
|
+
const codeEnd = lineStart;
|
|
177
|
+
const blockEnd = lineStart + line.length;
|
|
178
|
+
|
|
179
|
+
// Check if cursor is within this block
|
|
180
|
+
if (pos >= blockStart && pos <= blockEnd) {
|
|
181
|
+
return {
|
|
182
|
+
language: blockLanguage || 'text',
|
|
183
|
+
code: content.slice(codeStart, codeEnd),
|
|
184
|
+
start: blockStart,
|
|
185
|
+
end: blockEnd,
|
|
186
|
+
codeStart,
|
|
187
|
+
codeEnd,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
inBlock = false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
charOffset += line.length + 1;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
145
201
|
// Get service URLs from orchestrator
|
|
146
202
|
let syncUrl;
|
|
147
203
|
let runtimeUrls = {};
|
|
@@ -282,14 +338,16 @@ export async function createStudio(target, options = {}) {
|
|
|
282
338
|
const aiExtensions = mrmd.default.ai.aiIntegration({
|
|
283
339
|
onSparkClick: (e, view) => {
|
|
284
340
|
const context = mrmd.default.ai.getAiContext(view);
|
|
341
|
+
const codeBlock = detectCodeBlockAtCursor(view);
|
|
285
342
|
showAiMenu({
|
|
286
343
|
x: e.clientX,
|
|
287
344
|
y: e.clientY,
|
|
288
345
|
context: {
|
|
289
346
|
hasSelection: context.selectedText.length > 0,
|
|
290
|
-
inCodeCell:
|
|
347
|
+
inCodeCell: codeBlock !== null,
|
|
348
|
+
codeLanguage: codeBlock?.language || null,
|
|
291
349
|
},
|
|
292
|
-
onCommand: (cmd) => executeAiCommand(cmd, newEditor),
|
|
350
|
+
onCommand: (cmd) => executeAiCommand(cmd, newEditor, codeBlock),
|
|
293
351
|
juiceLevel: shellState.get('ai')?.juiceLevel || 0,
|
|
294
352
|
onJuiceLevelChange: (level) => {
|
|
295
353
|
shellState._set('ai.juiceLevel', level);
|
|
@@ -310,14 +368,20 @@ export async function createStudio(target, options = {}) {
|
|
|
310
368
|
|
|
311
369
|
/**
|
|
312
370
|
* Execute an AI command on the editor
|
|
371
|
+
* @param {Object} cmd - Command object
|
|
372
|
+
* @param {Object} targetEditor - Editor instance
|
|
373
|
+
* @param {Object|null} codeBlock - Code block info if cursor is in a code block
|
|
313
374
|
*/
|
|
314
|
-
async function executeAiCommand(cmd, targetEditor) {
|
|
375
|
+
async function executeAiCommand(cmd, targetEditor, codeBlock = null) {
|
|
315
376
|
if (!aiClient || !mrmd.default.ai) return;
|
|
316
377
|
|
|
317
378
|
const view = targetEditor.view;
|
|
318
379
|
const context = mrmd.default.ai.getAiContext(view);
|
|
319
380
|
const juiceLevel = shellState.get('ai')?.juiceLevel || 0;
|
|
320
381
|
|
|
382
|
+
// Detect language from code block, or fall back to python
|
|
383
|
+
const detectedLanguage = codeBlock?.language || 'python';
|
|
384
|
+
|
|
321
385
|
// Mark AI as active
|
|
322
386
|
shellState._set('ai.active', true);
|
|
323
387
|
|
|
@@ -341,7 +405,7 @@ export async function createStudio(target, options = {}) {
|
|
|
341
405
|
} else if (cmd.program.includes('Code')) {
|
|
342
406
|
params = {
|
|
343
407
|
code: context.selectedText,
|
|
344
|
-
language:
|
|
408
|
+
language: detectedLanguage,
|
|
345
409
|
local_context: context.localContext,
|
|
346
410
|
document_context: context.documentContext,
|
|
347
411
|
};
|
|
@@ -594,9 +658,7 @@ export async function createStudio(target, options = {}) {
|
|
|
594
658
|
},
|
|
595
659
|
|
|
596
660
|
async onRestartRuntime(language) {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
const docName = shellState.get('currentDoc');
|
|
661
|
+
const docName = currentDocName;
|
|
600
662
|
if (!docName) {
|
|
601
663
|
console.warn('[RestartRuntime] No current document');
|
|
602
664
|
return;
|
|
@@ -606,19 +668,15 @@ export async function createStudio(target, options = {}) {
|
|
|
606
668
|
// Get current session info to preserve venv
|
|
607
669
|
const python = shellState.get('runtimes.python') || {};
|
|
608
670
|
const currentVenv = python.venv;
|
|
609
|
-
console.log('[RestartRuntime] Current venv:', currentVenv);
|
|
610
671
|
|
|
611
672
|
// Destroy existing session (kills the daemon)
|
|
612
|
-
console.log('[RestartRuntime] Destroying session for:', docName);
|
|
613
673
|
await orchestratorClient.destroySession(docName);
|
|
614
674
|
|
|
615
675
|
// Small delay to ensure cleanup
|
|
616
676
|
await new Promise(r => setTimeout(r, 500));
|
|
617
677
|
|
|
618
678
|
// Create new session with same venv
|
|
619
|
-
console.log('[RestartRuntime] Creating new session with venv:', currentVenv);
|
|
620
679
|
const sessionInfo = await shellState.createSession(docName, 'dedicated', currentVenv);
|
|
621
|
-
console.log('[RestartRuntime] New session created:', sessionInfo);
|
|
622
680
|
|
|
623
681
|
// Reconnect editor to new runtime
|
|
624
682
|
if (editor?.connectRuntime && sessionInfo.url) {
|
|
@@ -636,6 +694,31 @@ export async function createStudio(target, options = {}) {
|
|
|
636
694
|
}
|
|
637
695
|
},
|
|
638
696
|
|
|
697
|
+
async onKillRuntime(runtimeId) {
|
|
698
|
+
try {
|
|
699
|
+
const result = await orchestratorClient.killRuntime(runtimeId);
|
|
700
|
+
|
|
701
|
+
// Refresh state after kill
|
|
702
|
+
await shellState.refresh();
|
|
703
|
+
|
|
704
|
+
// If we killed the current document's runtime, update the editor
|
|
705
|
+
if (runtimeId === currentDocName && editor?.execution) {
|
|
706
|
+
// Clear the runtime connection
|
|
707
|
+
editor.execution.setRuntimeUrl(null);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
emit('runtimeKilled', { runtimeId, result });
|
|
711
|
+
} catch (error) {
|
|
712
|
+
console.error('[KillRuntime] Error:', error);
|
|
713
|
+
await confirm({
|
|
714
|
+
title: 'Error',
|
|
715
|
+
message: `Failed to kill runtime: ${error.message}`,
|
|
716
|
+
confirmLabel: 'OK',
|
|
717
|
+
cancelLabel: '',
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
},
|
|
721
|
+
|
|
639
722
|
async onNewFile() {
|
|
640
723
|
const newName = await prompt({
|
|
641
724
|
title: 'New File',
|
|
@@ -819,10 +902,10 @@ export async function createStudio(target, options = {}) {
|
|
|
819
902
|
// Create status bar
|
|
820
903
|
let statusBarComponent = null;
|
|
821
904
|
if (statusBarConfig.enabled !== false) {
|
|
822
|
-
// Default segments -
|
|
905
|
+
// Default segments - 'runtimes' for project-wide management, 'ai' if available
|
|
823
906
|
const defaultSegments = aiClient
|
|
824
|
-
? ['files', '
|
|
825
|
-
: ['files', '
|
|
907
|
+
? ['files', 'runtimes', 'sync', 'ai']
|
|
908
|
+
: ['files', 'runtimes', 'sync'];
|
|
826
909
|
|
|
827
910
|
statusBarComponent = createStatusBar({
|
|
828
911
|
container: statusBarContainer,
|
|
@@ -321,6 +321,45 @@ export class OrchestratorClient {
|
|
|
321
321
|
});
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
+
/**
|
|
325
|
+
* List all active sessions
|
|
326
|
+
* @returns {Promise<{sessions: Array}>}
|
|
327
|
+
*/
|
|
328
|
+
async listSessions() {
|
|
329
|
+
return this._fetch('/api/sessions');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ===========================================================================
|
|
333
|
+
// Project & Runtime Management
|
|
334
|
+
// ===========================================================================
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Get project information
|
|
338
|
+
* @returns {Promise<{root: string, name: string, type: string, venv: string|null}>}
|
|
339
|
+
*/
|
|
340
|
+
async getProject() {
|
|
341
|
+
return this._fetch('/api/project');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* List all runtimes (shared and dedicated)
|
|
346
|
+
* @returns {Promise<{shared: Object|null, dedicated: Array, sessions: Array, project: Object}>}
|
|
347
|
+
*/
|
|
348
|
+
async listRuntimes() {
|
|
349
|
+
return this._fetch('/api/runtimes');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Kill a runtime
|
|
354
|
+
* @param {string} runtimeId - Runtime ID ('shared' or document name for dedicated)
|
|
355
|
+
* @returns {Promise<{id: string, killed: boolean, message: string}>}
|
|
356
|
+
*/
|
|
357
|
+
async killRuntime(runtimeId) {
|
|
358
|
+
return this._fetch(`/api/runtimes/${encodeURIComponent(runtimeId)}`, {
|
|
359
|
+
method: 'DELETE',
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
324
363
|
// ===========================================================================
|
|
325
364
|
// Logs
|
|
326
365
|
// ===========================================================================
|
package/src/shell/state.js
CHANGED
|
@@ -59,6 +59,13 @@ function getInitialState() {
|
|
|
59
59
|
projectRoot: '',
|
|
60
60
|
file: null,
|
|
61
61
|
theme: null, // null = auto, or theme name
|
|
62
|
+
// Project info from orchestrator
|
|
63
|
+
project: {
|
|
64
|
+
root: '',
|
|
65
|
+
name: '',
|
|
66
|
+
type: 'unknown',
|
|
67
|
+
venv: null,
|
|
68
|
+
},
|
|
62
69
|
runtimes: {
|
|
63
70
|
// Legacy: single Python runtime info (for backward compat)
|
|
64
71
|
python: null,
|
|
@@ -279,15 +286,20 @@ export class ShellStateManager {
|
|
|
279
286
|
*/
|
|
280
287
|
async refresh() {
|
|
281
288
|
try {
|
|
282
|
-
// Fetch status and
|
|
283
|
-
const [status, env] = await Promise.all([
|
|
289
|
+
// Fetch status, environment, and project info in parallel
|
|
290
|
+
const [status, env, project] = await Promise.all([
|
|
284
291
|
this._client.getStatus(),
|
|
285
292
|
this._client.getEnvironment(),
|
|
293
|
+
this._client.getProject().catch(() => null),
|
|
286
294
|
]);
|
|
287
295
|
|
|
288
296
|
this._updateFromOrchestratorStatus(status);
|
|
289
297
|
this._updateFromEnvironment(env);
|
|
290
298
|
|
|
299
|
+
if (project) {
|
|
300
|
+
this._set('project', project);
|
|
301
|
+
}
|
|
302
|
+
|
|
291
303
|
this._set('orchestrator.status', 'connected');
|
|
292
304
|
this._set('orchestrator.error', null);
|
|
293
305
|
} catch (error) {
|