maka-agent 0.2.0-dev.52.20260926 → 0.2.0-dev.53.20260927
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/dist/activation-command.js +11 -3
- package/dist/pi-tui-mcp-status.js +73 -5
- package/dist/pi-tui-runner.js +1 -1
- package/dist/runtime-host-run-command.js +39 -6
- package/dist/runtime-host-run-mcp.js +161 -0
- package/dist/runtime-host-session-driver.js +22 -2
- package/dist/tui-copy-catalog.js +6 -0
- package/dist/tui-mcp-control.js +11 -2
- package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
- package/node_modules/@maka/core/dist/attachments.js +7 -0
- package/node_modules/@maka/core/dist/events.js +11 -0
- package/node_modules/@maka/core/dist/provider-registry.js +11 -2
- package/node_modules/@maka/core/dist/turn-origin.js +6 -0
- package/node_modules/@maka/runtime/dist/ai-sdk-compaction.js +1 -5
- package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +80 -24
- package/node_modules/@maka/runtime/dist/bots/proxied-fetch.js +5 -4
- package/node_modules/@maka/runtime/dist/computer-use-tools.js +136 -100
- package/node_modules/@maka/runtime/dist/context-budget-helpers.js +4 -0
- package/node_modules/@maka/runtime/dist/conversation-copy.js +51 -205
- package/node_modules/@maka/runtime/dist/edit-replace.js +7 -7
- package/node_modules/@maka/runtime/dist/filesystem-executor.js +4 -20
- package/node_modules/@maka/runtime/dist/filesystem-worker/operations.js +17 -28
- package/node_modules/@maka/runtime/dist/format-json.js +39 -0
- package/node_modules/@maka/runtime/dist/history-compact-checkpoint.js +1 -6
- package/node_modules/@maka/runtime/dist/image-file.js +22 -14
- package/node_modules/@maka/runtime/dist/model-adapter.js +18 -2
- package/node_modules/@maka/runtime/dist/model-history.js +201 -3
- package/node_modules/@maka/runtime/dist/network/abortable-connector.js +55 -0
- package/node_modules/@maka/runtime/dist/network/proxy-dispatcher.js +72 -37
- package/node_modules/@maka/runtime/dist/network/proxy-test.js +1 -1
- package/node_modules/@maka/runtime/dist/network/scoped-fetch-transport.js +7 -2
- package/node_modules/@maka/runtime/dist/openai-codex-history-compactor.js +1 -3
- package/node_modules/@maka/runtime/dist/provider-error-classification.js +36 -8
- package/node_modules/@maka/runtime/dist/recovery-resolver.js +1 -91
- package/node_modules/@maka/runtime/dist/runtime-kernel.js +44 -0
- package/node_modules/@maka/runtime/dist/session-manager.js +54 -58
- package/node_modules/@maka/runtime/dist/subscription-model-fetch.js +84 -2
- package/node_modules/@maka/runtime/dist/workers/filesystem-worker.js +63 -51
- package/node_modules/@maka/runtime/dist/workspace-executor.js +5 -6
- package/node_modules/@maka/runtime-host/dist/adapter/session-projector.js +27 -6
- package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +4 -1
- package/node_modules/@maka/runtime-host/dist/client/host-handoff.js +9 -9
- package/node_modules/@maka/runtime-host/dist/client/session-catalog-summary.js +9 -1
- package/node_modules/@maka/runtime-host/dist/protocol/index.js +12 -1
- package/node_modules/@maka/runtime-host/dist/protocol/session-catalog.js +28 -6
- package/node_modules/@maka/runtime-host/dist/protocol/turn.js +12 -2
- package/node_modules/@maka/runtime-host/dist/server/client-capability-coordinator.js +10 -7
- package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +10 -2
- package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +2 -0
- package/node_modules/@maka/runtime-host/dist/server/interactive-turn-coordinator.js +4 -1
- package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +4 -2
- package/node_modules/@maka/runtime-host/dist/server/operation-dispatcher.js +4 -0
- package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +6 -5
- package/node_modules/@maka/runtime-host/dist/server/safe-boundary-resume-policy.js +27 -0
- package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +5 -3
- package/node_modules/@maka/runtime-host/dist/server/session-continuity-coordinator.js +7 -2
- package/node_modules/@maka/runtime-host/dist/server/session-revision-coordinator.js +11 -141
- package/node_modules/@maka/runtime-host/dist/server/session-revision-graph-references.js +46 -75
- package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +18 -2
- package/node_modules/@maka/runtime-host/dist/server/workhub-result-coordinator.js +33 -3
- package/node_modules/@maka/runtime-host/dist/transport/framed-byte-stream-transport.js +39 -5
- package/node_modules/@maka/storage/dist/agent-run-store-contract.js +7 -1
- package/node_modules/@maka/storage/dist/artifact-store.js +1 -5
- package/node_modules/@maka/storage/dist/artifact-stores.js +0 -3
- package/node_modules/@maka/storage/dist/atomic-file-write.js +1 -0
- package/node_modules/@maka/storage/dist/claude-code-session-adapter.js +107 -57
- package/node_modules/@maka/storage/dist/codex-session-adapter.js +227 -19
- package/node_modules/@maka/storage/dist/external-session-importer.js +1 -1
- package/node_modules/@maka/storage/dist/mcp-config-store.js +22 -3
- package/node_modules/@maka/storage/dist/settings-store.js +281 -13
- package/package.json +1 -1
|
@@ -321,7 +321,11 @@ export async function runMakaActivationCli(argv, overrides = {}) {
|
|
|
321
321
|
}, options.timeoutMs);
|
|
322
322
|
try {
|
|
323
323
|
const text = activationStimulusText(request.stimulus);
|
|
324
|
-
const stream = await activationStream(context.runtime, session.id, {
|
|
324
|
+
const stream = await activationStream(context.runtime, session.id, {
|
|
325
|
+
turnId: deps.newId(),
|
|
326
|
+
text,
|
|
327
|
+
origin: { kind: 'cloud_activation', activationId: request.activationId },
|
|
328
|
+
}, existing !== undefined && deps.automatedResumeEnabled());
|
|
325
329
|
const drain = (async () => {
|
|
326
330
|
for await (const event of stream) {
|
|
327
331
|
if (sessionEventSandboxBoundaryFailureReason(event))
|
|
@@ -504,8 +508,8 @@ function activationStimulusText(stimulus) {
|
|
|
504
508
|
}
|
|
505
509
|
return JSON.stringify({ type: stimulus.type, payload });
|
|
506
510
|
}
|
|
507
|
-
async function activationStream(runtime, sessionId, input,
|
|
508
|
-
if (
|
|
511
|
+
async function activationStream(runtime, sessionId, input, allowAutomatedResume) {
|
|
512
|
+
if (allowAutomatedResume && runtime.resumeLatest) {
|
|
509
513
|
const resumed = await runtime.resumeLatest(sessionId);
|
|
510
514
|
if (resumed)
|
|
511
515
|
return resumed;
|
|
@@ -562,6 +566,10 @@ function makaActivateHelpText() {
|
|
|
562
566
|
}
|
|
563
567
|
function defaultMakaActivationDeps() {
|
|
564
568
|
return {
|
|
569
|
+
automatedResumeEnabled: () => {
|
|
570
|
+
const value = process.env.MAKA_RUNTIME_SAFE_BOUNDARY_RESUME;
|
|
571
|
+
return value === '1' || value === 'true';
|
|
572
|
+
},
|
|
565
573
|
createContext: createRuntimeHostActivationContext,
|
|
566
574
|
listSessions: listRuntimeHostActivationSessions,
|
|
567
575
|
workspaceRoot: () => resolve(process.cwd()),
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* specific language governing permissions and limitations
|
|
17
17
|
* under the License.
|
|
18
18
|
*/
|
|
19
|
-
import { Editor, Input, Key, matchesKey, truncateToWidth, visibleWidth, } from '@earendil-works/pi-tui';
|
|
19
|
+
import { Editor, Input, Key, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from '@earendil-works/pi-tui';
|
|
20
20
|
import { generalizedErrorMessageForLocale } from '@maka/core/redaction';
|
|
21
21
|
import { defineUiMessageCatalog, formatUiMessage, resolveUiMessageCatalog, } from '@maka/core/ui-locale';
|
|
22
22
|
import { normalizeMcpConfig } from '@maka/storage/mcp-config-store';
|
|
@@ -40,6 +40,7 @@ export class McpManagementOverlay {
|
|
|
40
40
|
editor;
|
|
41
41
|
closed = false;
|
|
42
42
|
actionAttempt = 0;
|
|
43
|
+
pendingConfigErrorPath;
|
|
43
44
|
constructor(input) {
|
|
44
45
|
this.input = input;
|
|
45
46
|
this.dispose = input.surface?.subscribe(input.onChange) ?? (() => undefined);
|
|
@@ -59,11 +60,9 @@ export class McpManagementOverlay {
|
|
|
59
60
|
}
|
|
60
61
|
if (this.phase.kind === 'busy') {
|
|
61
62
|
if (matchesKey(data, Key.escape)) {
|
|
62
|
-
this.actionAttempt += 1;
|
|
63
63
|
this.backToList();
|
|
64
64
|
}
|
|
65
65
|
else if (matchesKey(data, 'q')) {
|
|
66
|
-
this.actionAttempt += 1;
|
|
67
66
|
this.close();
|
|
68
67
|
}
|
|
69
68
|
return;
|
|
@@ -75,7 +74,9 @@ export class McpManagementOverlay {
|
|
|
75
74
|
this.backToList();
|
|
76
75
|
return;
|
|
77
76
|
}
|
|
78
|
-
if (this.phase.kind === '
|
|
77
|
+
if (this.phase.kind === 'config_error')
|
|
78
|
+
this.handleTextScroll(data);
|
|
79
|
+
else if (this.phase.kind === 'list')
|
|
79
80
|
this.handleListInput(data);
|
|
80
81
|
else if (this.phase.kind === 'add_choice')
|
|
81
82
|
this.handleAddChoice(data);
|
|
@@ -134,6 +135,14 @@ export class McpManagementOverlay {
|
|
|
134
135
|
handleListInput(data) {
|
|
135
136
|
const snapshot = this.input.surface?.snapshot();
|
|
136
137
|
const servers = snapshot?.servers ?? [];
|
|
138
|
+
if (snapshot?.initialization === 'error') {
|
|
139
|
+
this.handleTextScroll(data);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
// A ready empty list still has explanatory text below the publication
|
|
143
|
+
// status, which must remain reachable in a short terminal.
|
|
144
|
+
if (servers.length === 0 && this.handleTextScroll(data))
|
|
145
|
+
return;
|
|
137
146
|
if (matchesKey(data, Key.up)) {
|
|
138
147
|
this.selected = clamp(this.selected - 1, 0, servers.length - 1);
|
|
139
148
|
}
|
|
@@ -187,6 +196,25 @@ export class McpManagementOverlay {
|
|
|
187
196
|
}
|
|
188
197
|
this.input.onChange();
|
|
189
198
|
}
|
|
199
|
+
handleTextScroll(data) {
|
|
200
|
+
if (matchesKey(data, Key.up))
|
|
201
|
+
this.top -= 1;
|
|
202
|
+
else if (matchesKey(data, Key.down))
|
|
203
|
+
this.top += 1;
|
|
204
|
+
else if (matchesKey(data, Key.pageUp))
|
|
205
|
+
this.top -= Math.max(1, this.bodyRows);
|
|
206
|
+
else if (matchesKey(data, Key.pageDown))
|
|
207
|
+
this.top += Math.max(1, this.bodyRows);
|
|
208
|
+
else if (matchesKey(data, Key.home))
|
|
209
|
+
this.top = 0;
|
|
210
|
+
else if (matchesKey(data, Key.end))
|
|
211
|
+
this.top = this.maxTop();
|
|
212
|
+
else
|
|
213
|
+
return false;
|
|
214
|
+
this.top = clamp(this.top, 0, this.maxTop());
|
|
215
|
+
this.input.onChange();
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
190
218
|
handleAddChoice(data) {
|
|
191
219
|
if (matchesKey(data, 'g'))
|
|
192
220
|
this.startInput('server_id', { serverId: '' });
|
|
@@ -352,8 +380,24 @@ export class McpManagementOverlay {
|
|
|
352
380
|
}
|
|
353
381
|
if (this.closed || attempt !== this.actionAttempt)
|
|
354
382
|
return;
|
|
383
|
+
if (result.status === 'failed' && result.reason === 'invalid-config-file') {
|
|
384
|
+
// Esc dismisses the busy view, not the operation or its file diagnostic.
|
|
385
|
+
// Defer presentation while the user is editing another form.
|
|
386
|
+
this.pendingConfigErrorPath = result.path;
|
|
387
|
+
this.showPendingConfigError();
|
|
388
|
+
this.input.onChange();
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (result.status === 'applied' &&
|
|
392
|
+
['add', 'edit', 'commit_import', 'set_enabled', 'remove'].includes(action.kind)) {
|
|
393
|
+
this.pendingConfigErrorPath = undefined;
|
|
394
|
+
}
|
|
395
|
+
// Do not restore a dismissed busy view for ordinary completion results.
|
|
396
|
+
if (this.phase.kind !== 'busy')
|
|
397
|
+
return;
|
|
355
398
|
this.phase = { kind: 'list' };
|
|
356
399
|
this.notice = actionNotice(result, this.input.locale);
|
|
400
|
+
this.showPendingConfigError();
|
|
357
401
|
this.input.onChange();
|
|
358
402
|
}
|
|
359
403
|
document(width) {
|
|
@@ -361,6 +405,9 @@ export class McpManagementOverlay {
|
|
|
361
405
|
const snapshot = this.input.surface?.snapshot();
|
|
362
406
|
if (!snapshot)
|
|
363
407
|
return unavailableDocument(this.input.locale);
|
|
408
|
+
if (this.phase.kind === 'config_error') {
|
|
409
|
+
return wrapTextWithAnsi(invalidConfigFileCopy(this.input.locale, this.phase.path), width);
|
|
410
|
+
}
|
|
364
411
|
if (this.phase.kind === 'input')
|
|
365
412
|
return this.inputDocument(width);
|
|
366
413
|
const editor = MCP_STATUS_COPY[this.input.locale].editor;
|
|
@@ -407,7 +454,11 @@ export class McpManagementOverlay {
|
|
|
407
454
|
if (snapshot.initialization === 'loading')
|
|
408
455
|
return [...lines, loadingCopy(this.input.locale)];
|
|
409
456
|
if (snapshot.initialization === 'error') {
|
|
410
|
-
|
|
457
|
+
lines.push(ansi.red(loadErrorCopy(this.input.locale)));
|
|
458
|
+
if (snapshot.invalidConfigPath) {
|
|
459
|
+
lines.push(...wrapTextWithAnsi(invalidConfigFileCopy(this.input.locale, snapshot.invalidConfigPath), width));
|
|
460
|
+
}
|
|
461
|
+
return lines;
|
|
411
462
|
}
|
|
412
463
|
if (snapshot.servers.length === 0)
|
|
413
464
|
return [...lines, '', emptyCopy(this.input.locale)];
|
|
@@ -436,8 +487,12 @@ export class McpManagementOverlay {
|
|
|
436
487
|
}
|
|
437
488
|
footer() {
|
|
438
489
|
const copy = MCP_STATUS_COPY[this.input.locale].footer;
|
|
490
|
+
if (this.phase.kind === 'config_error')
|
|
491
|
+
return copy.diagnostic;
|
|
439
492
|
if (this.phase.kind !== 'list')
|
|
440
493
|
return copy.back;
|
|
494
|
+
if (this.input.surface?.snapshot().initialization === 'error')
|
|
495
|
+
return copy.readOnly;
|
|
441
496
|
if (!this.management())
|
|
442
497
|
return copy.readOnly;
|
|
443
498
|
return this.input.surface?.snapshot().canManagePublicationCredential
|
|
@@ -452,8 +507,18 @@ export class McpManagementOverlay {
|
|
|
452
507
|
this.phase = { kind: 'list' };
|
|
453
508
|
if (clearNotice)
|
|
454
509
|
this.notice = undefined;
|
|
510
|
+
this.showPendingConfigError();
|
|
455
511
|
this.input.onChange();
|
|
456
512
|
}
|
|
513
|
+
showPendingConfigError() {
|
|
514
|
+
if (this.pendingConfigErrorPath === undefined ||
|
|
515
|
+
(this.phase.kind !== 'list' && this.phase.kind !== 'busy'))
|
|
516
|
+
return;
|
|
517
|
+
this.phase = { kind: 'config_error', path: this.pendingConfigErrorPath };
|
|
518
|
+
this.pendingConfigErrorPath = undefined;
|
|
519
|
+
this.notice = undefined;
|
|
520
|
+
this.top = 0;
|
|
521
|
+
}
|
|
457
522
|
clearEditor() {
|
|
458
523
|
if (!this.editor)
|
|
459
524
|
return;
|
|
@@ -624,6 +689,9 @@ function actionNotice(result, locale) {
|
|
|
624
689
|
function resultCopy(locale, code) {
|
|
625
690
|
return MCP_STATUS_COPY[locale].editor.results[code] ?? code;
|
|
626
691
|
}
|
|
692
|
+
function invalidConfigFileCopy(locale, path) {
|
|
693
|
+
return formatUiMessage(MCP_STATUS_COPY[locale].invalidConfigFile, { path: path.replace(/[\u0000-\u001f\u007f-\u009f]/gu, '') }, locale);
|
|
694
|
+
}
|
|
627
695
|
function confirmAddDocument(draft, locale) {
|
|
628
696
|
const editor = MCP_STATUS_COPY[locale].editor;
|
|
629
697
|
return [
|
package/dist/pi-tui-runner.js
CHANGED
|
@@ -111,7 +111,7 @@ export function safeBoundaryResumeParkedCopy(reason) {
|
|
|
111
111
|
case 'resume_feature_disabled':
|
|
112
112
|
return {
|
|
113
113
|
level: 'info',
|
|
114
|
-
text: 'Safe-boundary resume is
|
|
114
|
+
text: 'Safe-boundary resume is disabled by this runtime policy.',
|
|
115
115
|
};
|
|
116
116
|
case 'resume_candidate_missing':
|
|
117
117
|
return {
|
|
@@ -18,11 +18,12 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { failureClassFromCompleteStopReason } from '@maka/core/events';
|
|
20
20
|
import { findProjectByIdentity } from '@maka/core/project';
|
|
21
|
-
import { projectSessionCatalogSummary, readRuntimeHostSessions, readRuntimeHostProjects, RuntimeHostOperationError, } from '@maka/runtime-host/client';
|
|
21
|
+
import { projectSessionCatalogSummary, readRuntimeHostSessions, readRuntimeHostProjects, RuntimeHostOperationError, isRuntimeHostReconnectingConnection, } from '@maka/runtime-host/client';
|
|
22
22
|
import { runtimeHostProfileUsesHostWorkspace } from '@maka/runtime-host/profile-kind';
|
|
23
23
|
import { runMakaTextCliCore, } from './run-command-core.js';
|
|
24
24
|
import { connectRuntimeHostCli, resolveRuntimeHostCliTarget, } from './runtime-host-cli-context.js';
|
|
25
25
|
import { createRuntimeHostMakaSessionDriver, } from './runtime-host-session-driver.js';
|
|
26
|
+
import { RuntimeHostRunMcp } from './runtime-host-run-mcp.js';
|
|
26
27
|
import { formatRuntimeHostCliTaskBlockers, isRuntimeHostCliTaskBlocked, readRuntimeHostCliTaskReadiness, } from './runtime-host-task-readiness.js';
|
|
27
28
|
import { resolveMakaClientDataRoot } from './workspace-root.js';
|
|
28
29
|
const GRAPH_POLL_INTERVAL_MS = 25;
|
|
@@ -70,6 +71,10 @@ export function createRuntimeHostRunContext(connection, catalog, input, override
|
|
|
70
71
|
createDriver: createRuntimeHostMakaSessionDriver,
|
|
71
72
|
...overrides,
|
|
72
73
|
};
|
|
74
|
+
const mcp = (!input.hostProfileId || input.hostProfileId === 'local') &&
|
|
75
|
+
isRuntimeHostReconnectingConnection(connection)
|
|
76
|
+
? new RuntimeHostRunMcp(input.workspaceRoot, connection)
|
|
77
|
+
: undefined;
|
|
73
78
|
const driver = contextDeps.createDriver({
|
|
74
79
|
connection,
|
|
75
80
|
cwd: input.cwd,
|
|
@@ -80,8 +85,9 @@ export function createRuntimeHostRunContext(connection, catalog, input, override
|
|
|
80
85
|
? { kind: 'client_path' }
|
|
81
86
|
: { kind: 'host' },
|
|
82
87
|
...(input.projectId ? { workspace: { kind: 'project', projectId: input.projectId } } : {}),
|
|
88
|
+
...(mcp ? { prepareSession: (sessionId) => mcp.prepare(sessionId) } : {}),
|
|
83
89
|
});
|
|
84
|
-
const runtime = new RuntimeHostRunRuntime(connection, driver, input.runOutcomeObserver, input.enableAgentGraph === true, input.sessionCwdOverride, input.maxSteps);
|
|
90
|
+
const runtime = new RuntimeHostRunRuntime(connection, driver, mcp, input.runOutcomeObserver, input.enableAgentGraph === true, input.sessionCwdOverride, input.maxSteps);
|
|
85
91
|
return {
|
|
86
92
|
runtime,
|
|
87
93
|
target: { connection: { slug: target.connection.slug }, model: target.model },
|
|
@@ -129,6 +135,7 @@ async function prepareRuntimeHostRunInput(connection, catalog, profile, input, c
|
|
|
129
135
|
class RuntimeHostRunRuntime {
|
|
130
136
|
#connection;
|
|
131
137
|
#driver;
|
|
138
|
+
#mcp;
|
|
132
139
|
#observer;
|
|
133
140
|
#graphEnabled;
|
|
134
141
|
#sessionCwdOverride;
|
|
@@ -142,9 +149,10 @@ class RuntimeHostRunRuntime {
|
|
|
142
149
|
#graphAdmissionTurnIds = new Set();
|
|
143
150
|
#latestTranscriptReplacement;
|
|
144
151
|
#graphTerminalWaiters = new Map();
|
|
145
|
-
constructor(connection, driver, observer, graphEnabled, sessionCwdOverride, maxSteps) {
|
|
152
|
+
constructor(connection, driver, mcp, observer, graphEnabled, sessionCwdOverride, maxSteps) {
|
|
146
153
|
this.#connection = connection;
|
|
147
154
|
this.#driver = driver;
|
|
155
|
+
this.#mcp = mcp;
|
|
148
156
|
this.#observer = observer;
|
|
149
157
|
this.#graphEnabled = graphEnabled;
|
|
150
158
|
this.#sessionCwdOverride = sessionCwdOverride;
|
|
@@ -166,6 +174,7 @@ class RuntimeHostRunRuntime {
|
|
|
166
174
|
}
|
|
167
175
|
async *sendMessage(sessionId, input) {
|
|
168
176
|
await this.#attach(sessionId);
|
|
177
|
+
await this.#mcp?.ready();
|
|
169
178
|
if (this.#stopRequested)
|
|
170
179
|
throw new Error('Turn was cancelled before start');
|
|
171
180
|
if (input.turnOrchestration?.mode === 'graph') {
|
|
@@ -176,6 +185,7 @@ class RuntimeHostRunRuntime {
|
|
|
176
185
|
turnId: input.turnId,
|
|
177
186
|
...(input.turnOrchestration ? { turnOrchestration: input.turnOrchestration } : {}),
|
|
178
187
|
...(maxSteps !== undefined ? { maxSteps } : {}),
|
|
188
|
+
...(input.origin !== undefined ? { origin: input.origin } : {}),
|
|
179
189
|
});
|
|
180
190
|
if (!turn.runId)
|
|
181
191
|
throw new Error('Runtime Host did not return a Run identity');
|
|
@@ -205,8 +215,30 @@ class RuntimeHostRunRuntime {
|
|
|
205
215
|
}
|
|
206
216
|
async resumeLatest(sessionId) {
|
|
207
217
|
await this.#attach(sessionId);
|
|
218
|
+
await this.#mcp?.ready();
|
|
208
219
|
const plan = await this.#connection.request('turn.resume.query', { sessionId });
|
|
209
|
-
return plan.disposition === 'ready' ? this.#
|
|
220
|
+
return plan.disposition === 'ready' ? this.#resumeAndObserve(plan) : null;
|
|
221
|
+
}
|
|
222
|
+
async *#resumeAndObserve(plan) {
|
|
223
|
+
const turn = await this.#driver.resumeLatestTurn(plan);
|
|
224
|
+
if (!turn.runId)
|
|
225
|
+
throw new Error('Runtime Host did not return a Run identity');
|
|
226
|
+
const activeTurn = {
|
|
227
|
+
sessionId: turn.sessionId,
|
|
228
|
+
turnId: turn.turnId,
|
|
229
|
+
runId: turn.runId,
|
|
230
|
+
outcome: new TurnOutcomeClassifier(turn.runId),
|
|
231
|
+
};
|
|
232
|
+
this.#activeTurn = activeTurn;
|
|
233
|
+
try {
|
|
234
|
+
if (this.#stopRequested)
|
|
235
|
+
await this.#stopTurn(activeTurn);
|
|
236
|
+
yield* this.#observeTurn(turn, activeTurn);
|
|
237
|
+
}
|
|
238
|
+
finally {
|
|
239
|
+
if (this.#activeTurn === activeTurn)
|
|
240
|
+
this.#activeTurn = undefined;
|
|
241
|
+
}
|
|
210
242
|
}
|
|
211
243
|
async stopSession(sessionId) {
|
|
212
244
|
this.#stopRequested = true;
|
|
@@ -273,16 +305,17 @@ class RuntimeHostRunRuntime {
|
|
|
273
305
|
if (outcome)
|
|
274
306
|
await this.#observer?.(outcome);
|
|
275
307
|
}
|
|
276
|
-
close() {
|
|
308
|
+
async close() {
|
|
277
309
|
this.#closed = true;
|
|
278
310
|
this.#interactions.close();
|
|
279
311
|
this.#unsubscribeTranscriptReplacements();
|
|
280
312
|
this.#cancelGraphTerminalWaiters(new Error('Runtime Host run context closed'));
|
|
281
|
-
|
|
313
|
+
await this.#mcp?.close();
|
|
282
314
|
}
|
|
283
315
|
async #attach(sessionId) {
|
|
284
316
|
if (this.#sessionId === sessionId)
|
|
285
317
|
return;
|
|
318
|
+
await this.#mcp?.prepare(sessionId);
|
|
286
319
|
const switched = await this.#driver.switchSession(sessionId);
|
|
287
320
|
if (this.#sessionCwdOverride?.sessionId === sessionId &&
|
|
288
321
|
switched.summary.cwd !== this.#sessionCwdOverride.cwd) {
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { createHash } from 'node:crypto';
|
|
20
|
+
import { stableJsonStringify } from '@maka/core/canonical-json';
|
|
21
|
+
import { createCredentialMcpOAuthStorage, McpClientManager } from '@maka/mcp';
|
|
22
|
+
import { createFileCredentialStore } from '@maka/storage/credential-store';
|
|
23
|
+
import { createMcpConfigStore } from '@maka/storage/mcp-config-store';
|
|
24
|
+
import { RuntimeHostOperationError, } from '@maka/runtime-host/client';
|
|
25
|
+
import { createMcpCapabilityProvider } from './mcp-capability-provider.js';
|
|
26
|
+
import { McpCapabilityPublication } from './mcp-capability-publication.js';
|
|
27
|
+
/** One headless invocation owns one Session's MCP processes and publication. */
|
|
28
|
+
export class RuntimeHostRunMcp {
|
|
29
|
+
#connection;
|
|
30
|
+
#workspaceRoot;
|
|
31
|
+
#manager;
|
|
32
|
+
#publication;
|
|
33
|
+
#unsubscribeManager;
|
|
34
|
+
#unsubscribeConnection;
|
|
35
|
+
#availability;
|
|
36
|
+
#sessionId;
|
|
37
|
+
#sessionConfigurationId;
|
|
38
|
+
#enabledServerIds = [];
|
|
39
|
+
#prepareTask;
|
|
40
|
+
#closed = false;
|
|
41
|
+
#closeTask;
|
|
42
|
+
constructor(workspaceRoot, connection) {
|
|
43
|
+
this.#workspaceRoot = workspaceRoot;
|
|
44
|
+
this.#connection = connection;
|
|
45
|
+
this.#manager = new McpClientManager({
|
|
46
|
+
clientName: 'maka-run',
|
|
47
|
+
excludedStdioEnvironmentKeys: ['MAKA_RUNTIME_HOST_ACCESS_CREDENTIAL'],
|
|
48
|
+
oauthStorage: createCredentialMcpOAuthStorage(createFileCredentialStore(workspaceRoot)),
|
|
49
|
+
});
|
|
50
|
+
this.#publication = new McpCapabilityPublication({
|
|
51
|
+
connectionIdentity: () => this.#availability?.kind === 'connected'
|
|
52
|
+
? `${this.#availability.hostEpoch}\0${this.#availability.connectionId}`
|
|
53
|
+
: undefined,
|
|
54
|
+
revision: () => this.#manager.toolSnapshot().revision,
|
|
55
|
+
createProvider: () => createMcpCapabilityProvider(this.#manager, {
|
|
56
|
+
admission: 'mcp',
|
|
57
|
+
onCurrentRegistrationRetired: () => this.#retire(),
|
|
58
|
+
}) ?? {
|
|
59
|
+
// Even an empty snapshot must replace a lost Session publication.
|
|
60
|
+
offers: () => [],
|
|
61
|
+
currentRegistrationRetired: () => this.#retire(),
|
|
62
|
+
},
|
|
63
|
+
replace: (provider) => this.#connection.replaceClientCapabilities(provider, {
|
|
64
|
+
sessionId: this.#sessionId,
|
|
65
|
+
sessionConfigurationId: this.#sessionConfigurationId,
|
|
66
|
+
}),
|
|
67
|
+
unregister: () => this.#connection.unregisterClientCapabilities({ sessionId: this.#sessionId }),
|
|
68
|
+
onState: () => undefined,
|
|
69
|
+
});
|
|
70
|
+
this.#unsubscribeManager = this.#manager.onChange(() => {
|
|
71
|
+
if (this.#sessionId && !this.#closed)
|
|
72
|
+
this.#publication.request();
|
|
73
|
+
});
|
|
74
|
+
this.#unsubscribeConnection = connection.subscribeConnectionAvailability((availability) => {
|
|
75
|
+
this.#availability = availability;
|
|
76
|
+
if (availability.kind !== 'connected')
|
|
77
|
+
this.#publication.invalidate();
|
|
78
|
+
if (this.#sessionId && !this.#closed)
|
|
79
|
+
this.#publication.request();
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
prepare(sessionId) {
|
|
83
|
+
if (this.#sessionId && this.#sessionId !== sessionId) {
|
|
84
|
+
return Promise.reject(new Error('MCP is already bound to another Session'));
|
|
85
|
+
}
|
|
86
|
+
this.#prepareTask ??= this.#prepare(sessionId);
|
|
87
|
+
return this.#prepareTask;
|
|
88
|
+
}
|
|
89
|
+
async #prepare(sessionId) {
|
|
90
|
+
if (this.#closed)
|
|
91
|
+
throw new Error('MCP publication is closed');
|
|
92
|
+
try {
|
|
93
|
+
const config = await createMcpConfigStore(this.#workspaceRoot).get();
|
|
94
|
+
this.#enabledServerIds = Object.entries(config.mcpServers)
|
|
95
|
+
.filter(([, server]) => server.enabled !== false)
|
|
96
|
+
.map(([serverId]) => serverId);
|
|
97
|
+
try {
|
|
98
|
+
await this.#manager.sync(config);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
throw new Error('Session MCP preparation failed; check the workspace MCP configuration', {
|
|
102
|
+
cause: error,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
this.#assertServersConnected();
|
|
106
|
+
if (this.#closed)
|
|
107
|
+
throw new Error('MCP publication is closed');
|
|
108
|
+
this.#sessionConfigurationId = `sha256:${createHash('sha256').update(stableJsonStringify(config)).digest('hex')}`;
|
|
109
|
+
this.#sessionId = sessionId;
|
|
110
|
+
await this.ready();
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
await this.close().catch(() => undefined);
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async ready() {
|
|
118
|
+
if (this.#closed || !this.#sessionId)
|
|
119
|
+
throw new Error('MCP publication is not prepared');
|
|
120
|
+
this.#assertServersConnected();
|
|
121
|
+
const state = await this.#publication.settle();
|
|
122
|
+
if (state !== 'published') {
|
|
123
|
+
if (this.#publication.lastError instanceof RuntimeHostOperationError &&
|
|
124
|
+
this.#publication.lastError.code === 'session_binding_conflict') {
|
|
125
|
+
throw new Error('Session MCP configuration conflicts with its frozen tools; restore the same configuration before resuming', { cause: this.#publication.lastError });
|
|
126
|
+
}
|
|
127
|
+
throw new Error('Session MCP capability publication is unavailable', {
|
|
128
|
+
cause: this.#publication.lastError,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
this.#assertServersConnected();
|
|
132
|
+
}
|
|
133
|
+
#assertServersConnected() {
|
|
134
|
+
const unavailable = this.#enabledServerIds.flatMap((serverId) => {
|
|
135
|
+
const state = this.#manager.status(serverId)?.state ?? 'missing';
|
|
136
|
+
return state === 'connected' ? [] : [`${serverId} (${state})`];
|
|
137
|
+
});
|
|
138
|
+
if (unavailable.length > 0) {
|
|
139
|
+
throw new Error(`Session MCP server(s) unavailable: ${unavailable.join(', ')}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
close() {
|
|
143
|
+
this.#closeTask ??= this.#close(true);
|
|
144
|
+
return this.#closeTask;
|
|
145
|
+
}
|
|
146
|
+
#retire() {
|
|
147
|
+
this.#closeTask ??= this.#close(false);
|
|
148
|
+
return this.#closeTask;
|
|
149
|
+
}
|
|
150
|
+
async #close(unregister) {
|
|
151
|
+
this.#closed = true;
|
|
152
|
+
this.#unsubscribeManager();
|
|
153
|
+
this.#unsubscribeConnection();
|
|
154
|
+
try {
|
|
155
|
+
await (unregister ? this.#publication.close() : this.#publication.retire());
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
await this.#manager.close();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -57,6 +57,7 @@ export function createRuntimeHostMakaSessionDriver(input) {
|
|
|
57
57
|
class RuntimeHostMakaSessionDriverImpl {
|
|
58
58
|
#connection;
|
|
59
59
|
#newId;
|
|
60
|
+
#prepareSession;
|
|
60
61
|
#now;
|
|
61
62
|
#inspectCwdChanges;
|
|
62
63
|
#executionLocation;
|
|
@@ -103,6 +104,7 @@ class RuntimeHostMakaSessionDriverImpl {
|
|
|
103
104
|
constructor(input) {
|
|
104
105
|
this.#connection = input.connection;
|
|
105
106
|
this.#newId = input.newId ?? randomUUID;
|
|
107
|
+
this.#prepareSession = input.prepareSession;
|
|
106
108
|
this.#now = input.now ?? Date.now;
|
|
107
109
|
this.#inspectCwdChanges = input.inspectCwdChanges ?? inspectGitCwdChanges;
|
|
108
110
|
this.#executionLocation = input.executionLocation ?? { kind: 'client_path' };
|
|
@@ -192,6 +194,9 @@ class RuntimeHostMakaSessionDriverImpl {
|
|
|
192
194
|
const events = channel.eventsForTurn(turnId);
|
|
193
195
|
const modelText = options.modelText ?? prompt;
|
|
194
196
|
try {
|
|
197
|
+
if (options.origin !== undefined && options.origin.kind !== 'cloud_activation') {
|
|
198
|
+
throw new Error('Runtime Host turn.start only supports cloud activation origins');
|
|
199
|
+
}
|
|
195
200
|
const startInput = {
|
|
196
201
|
sessionId,
|
|
197
202
|
turnId,
|
|
@@ -201,6 +206,7 @@ class RuntimeHostMakaSessionDriverImpl {
|
|
|
201
206
|
},
|
|
202
207
|
...(options.turnOrchestration ? { turnOrchestration: options.turnOrchestration } : {}),
|
|
203
208
|
...(options.maxSteps !== undefined ? { maxSteps: options.maxSteps } : {}),
|
|
209
|
+
...(options.origin !== undefined ? { origin: options.origin } : {}),
|
|
204
210
|
};
|
|
205
211
|
const result = await this.#connection.request('turn.start', startInput);
|
|
206
212
|
if (result.kind === 'blocked') {
|
|
@@ -325,6 +331,14 @@ class RuntimeHostMakaSessionDriverImpl {
|
|
|
325
331
|
if (plan.disposition !== 'ready') {
|
|
326
332
|
throw new SafeBoundaryResumeParkedError(plan.reason);
|
|
327
333
|
}
|
|
334
|
+
const turn = await this.resumeLatestTurn(plan);
|
|
335
|
+
yield* turn.events;
|
|
336
|
+
}
|
|
337
|
+
async resumeLatestTurn(plan) {
|
|
338
|
+
const sessionId = this.#requireSession('resume');
|
|
339
|
+
if (plan.sessionId !== sessionId) {
|
|
340
|
+
throw new Error('Runtime Host resume plan changed Session identity');
|
|
341
|
+
}
|
|
328
342
|
const channel = await this.#ensureChannel(sessionId);
|
|
329
343
|
const turnId = this.#newId();
|
|
330
344
|
this.#claimedTurnIds.add(turnId);
|
|
@@ -337,14 +351,19 @@ class RuntimeHostMakaSessionDriverImpl {
|
|
|
337
351
|
sourceRuntimeEventHighWater: plan.sourceRuntimeEventHighWater,
|
|
338
352
|
});
|
|
339
353
|
if (result.kind !== 'started') {
|
|
340
|
-
|
|
354
|
+
throw new SafeBoundaryResumeParkedError(result.plan.reason);
|
|
341
355
|
}
|
|
356
|
+
return {
|
|
357
|
+
sessionId: result.turn.sessionId,
|
|
358
|
+
turnId: result.turn.turnId,
|
|
359
|
+
runId: result.turn.runId,
|
|
360
|
+
events,
|
|
361
|
+
};
|
|
342
362
|
}
|
|
343
363
|
catch (error) {
|
|
344
364
|
channel.failTurn(turnId, error);
|
|
345
365
|
throw error;
|
|
346
366
|
}
|
|
347
|
-
yield* events;
|
|
348
367
|
}
|
|
349
368
|
submitMessage(text, options) {
|
|
350
369
|
return this.#admit(() => this.#submitMessage(text, options));
|
|
@@ -1051,6 +1070,7 @@ class RuntimeHostMakaSessionDriverImpl {
|
|
|
1051
1070
|
if (!this.#llmConnectionId) {
|
|
1052
1071
|
throw new Error('Runtime Host Session creation requires an exact Connection identity');
|
|
1053
1072
|
}
|
|
1073
|
+
await this.#prepareSession?.(sessionId);
|
|
1054
1074
|
const session = requireSession(await this.#request('session.create', {
|
|
1055
1075
|
sessionId,
|
|
1056
1076
|
workspace,
|
package/dist/tui-copy-catalog.js
CHANGED
|
@@ -246,6 +246,7 @@ export const TUI_COPY_RESOURCES = {
|
|
|
246
246
|
title: 'MCP SERVERS',
|
|
247
247
|
footer: {
|
|
248
248
|
back: 'Esc back',
|
|
249
|
+
diagnostic: '↑/↓ scroll · Esc back',
|
|
249
250
|
readOnly: '↑/↓ scroll · q/Esc close',
|
|
250
251
|
manage: 'a Add · Enter Edit · Space Enable/disable · t Test · r Reconnect · d Remove · Esc Close',
|
|
251
252
|
managePublication: 'a Add · Enter Edit · Space Enable/disable · t Test · r Reconnect · d Remove · p Set provider credential · x Remove credential · Esc Close',
|
|
@@ -254,6 +255,7 @@ export const TUI_COPY_RESOURCES = {
|
|
|
254
255
|
unavailableDetail: 'Client MCP tool association for remote Runtime Hosts is planned for a later release.',
|
|
255
256
|
loading: 'Loading mcp.json and discovering tools…',
|
|
256
257
|
loadError: 'MCP configuration could not be loaded; no tools were published to the Runtime Host.',
|
|
258
|
+
invalidConfigFile: 'Invalid JSON in {path}. The file is unchanged. Quit maka, back up and repair this file before retrying.',
|
|
257
259
|
noServers: 'No MCP servers are configured. Press a to add one.',
|
|
258
260
|
publication: {
|
|
259
261
|
waiting: 'waiting to publish',
|
|
@@ -345,6 +347,7 @@ export const TUI_COPY_RESOURCES = {
|
|
|
345
347
|
title: 'MCP 服务器',
|
|
346
348
|
footer: {
|
|
347
349
|
back: 'Esc 返回',
|
|
350
|
+
diagnostic: '↑/↓ 滚动 · Esc 返回',
|
|
348
351
|
readOnly: '↑/↓ 滚动 · q/Esc 关闭',
|
|
349
352
|
manage: 'a 添加 · Enter 编辑 · Space 启用/停用 · t 测试 · r 重连 · d 删除 · Esc 关闭',
|
|
350
353
|
managePublication: 'a 添加 · Enter 编辑 · Space 启用/停用 · t 测试 · r 重连 · d 删除 · p 设置 Provider 凭据 · x 删除凭据 · Esc 关闭',
|
|
@@ -353,6 +356,7 @@ export const TUI_COPY_RESOURCES = {
|
|
|
353
356
|
unavailableDetail: '远程 Runtime Host 的客户端 MCP 工具关联将在后续版本提供。',
|
|
354
357
|
loading: '正在读取 mcp.json 并发现工具…',
|
|
355
358
|
loadError: '无法读取或应用 MCP 配置;没有向 Runtime Host 发布工具。',
|
|
359
|
+
invalidConfigFile: '{path} 中的 JSON 无效,文件未被修改。请退出 maka,备份并修复此文件后重试。',
|
|
356
360
|
noServers: '尚未配置 MCP 服务器。按 a 添加。',
|
|
357
361
|
publication: {
|
|
358
362
|
waiting: '等待发布',
|
|
@@ -444,6 +448,7 @@ export const TUI_COPY_RESOURCES = {
|
|
|
444
448
|
title: 'MCP 伺服器',
|
|
445
449
|
footer: {
|
|
446
450
|
back: 'Esc 返回',
|
|
451
|
+
diagnostic: '↑/↓ 捲動 · Esc 返回',
|
|
447
452
|
readOnly: '↑/↓ 捲動 · q/Esc 關閉',
|
|
448
453
|
manage: 'a 新增 · Enter 編輯 · Space 啟用/停用 · t 測試 · r 重新連線 · d 刪除 · Esc 關閉',
|
|
449
454
|
managePublication: 'a 新增 · Enter 編輯 · Space 啟用/停用 · t 測試 · r 重新連線 · d 刪除 · p 設定 Provider 認證資料 · x 刪除認證資料 · Esc 關閉',
|
|
@@ -452,6 +457,7 @@ export const TUI_COPY_RESOURCES = {
|
|
|
452
457
|
unavailableDetail: '遠端 Runtime Host 的用戶端 MCP 工具關聯將於後續版本提供。',
|
|
453
458
|
loading: '正在讀取 mcp.json 並探索工具…',
|
|
454
459
|
loadError: '無法讀取或套用 MCP 設定;未向 Runtime Host 發佈任何工具。',
|
|
460
|
+
invalidConfigFile: '{path} 中的 JSON 無效,檔案未被修改。請退出 maka,備份並修復此檔案後重試。',
|
|
455
461
|
noServers: '尚未設定 MCP 伺服器。按 a 新增。',
|
|
456
462
|
publication: {
|
|
457
463
|
waiting: '等待發佈',
|
package/dist/tui-mcp-control.js
CHANGED
|
@@ -212,10 +212,16 @@ class TuiMcpControllerImpl {
|
|
|
212
212
|
this.#refreshManagerSnapshot('ready', 'ready');
|
|
213
213
|
this.#requestPublication();
|
|
214
214
|
}
|
|
215
|
-
catch {
|
|
215
|
+
catch (error) {
|
|
216
216
|
if (this.#closed)
|
|
217
217
|
return;
|
|
218
|
-
this.#updateSnapshot({
|
|
218
|
+
this.#updateSnapshot({
|
|
219
|
+
initialization: 'error',
|
|
220
|
+
publication: 'not_published',
|
|
221
|
+
...(error instanceof McpConfigSourceError && error.reason === 'invalid-json' && error.path
|
|
222
|
+
? { invalidConfigPath: error.path }
|
|
223
|
+
: {}),
|
|
224
|
+
});
|
|
219
225
|
}
|
|
220
226
|
}
|
|
221
227
|
#serializeAction(work) {
|
|
@@ -303,6 +309,9 @@ class TuiMcpControllerImpl {
|
|
|
303
309
|
return error.result;
|
|
304
310
|
if (error instanceof McpConfigurationValidationError)
|
|
305
311
|
return { status: 'failed', reason: 'invalid-config' };
|
|
312
|
+
if (error instanceof McpConfigSourceError && error.reason === 'invalid-json' && error.path) {
|
|
313
|
+
return { status: 'failed', reason: 'invalid-config-file', path: error.path };
|
|
314
|
+
}
|
|
306
315
|
if (error instanceof AtomicFileWriteCommitUnknownError) {
|
|
307
316
|
// The transform has already published, including any credential
|
|
308
317
|
// retirement. Reload its authority; never replay those effects.
|
|
Binary file
|
|
@@ -55,6 +55,13 @@ export function parseAttachmentResourceRef(value) {
|
|
|
55
55
|
}
|
|
56
56
|
/** Per-send cap on attachment count, shared by renderer preflight and main resolve. */
|
|
57
57
|
export const MAX_ATTACHMENT_COUNT = 8;
|
|
58
|
+
/**
|
|
59
|
+
* Per-drop cap on files a composer checks for folders before staging them
|
|
60
|
+
* (#5279). A larger drop or paste is refused whole instead of staging files
|
|
61
|
+
* nobody checked; it is far past MAX_ATTACHMENT_COUNT, so it could never be
|
|
62
|
+
* sent. Preload and main refuse a longer check request outright.
|
|
63
|
+
*/
|
|
64
|
+
export const MAX_ATTACHMENT_DROP_COUNT = 1024;
|
|
58
65
|
/** Per-file byte cap, shared by renderer preflight, preload encode, and main resolve. */
|
|
59
66
|
export const MAX_ATTACHMENT_BYTES = 50 * 1024 * 1024;
|
|
60
67
|
/** Raw-byte cap for workspace images returned by Read, leaving room for Base64 transport overhead. */
|