maka-agent 0.2.0-dev.45.20260921 → 0.2.0-dev.47.20260922
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/DISCLAIMER-WIP +6 -1
- package/README.md +6 -1
- package/dist/acp/maka-acp-agent.js +1 -1
- package/dist/acp/session-mcp.js +212 -0
- package/dist/acp/session-registry.js +52 -8
- package/dist/acp/stdio-server.js +3 -0
- package/dist/cli-core.js +1 -1
- package/dist/mcp-capability-provider.js +6 -1
- package/dist/mcp-capability-publication.js +139 -0
- package/dist/pi-tui-layout.js +46 -17
- package/dist/pi-tui-pickers.js +25 -14
- package/dist/pi-tui-runner.js +24 -15
- package/dist/runtime-host-service-manager.js +26 -2
- package/dist/tui-context-refresh.js +15 -26
- package/dist/tui-mcp-control.js +29 -88
- package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
- package/node_modules/@maka/core/dist/client-capability-grant.js +2 -2
- package/node_modules/@maka/core/dist/model-thinking.js +36 -1
- package/node_modules/@maka/core/dist/provider-registry.js +0 -1
- package/node_modules/@maka/core/dist/refresh-read-coordinator.js +62 -0
- package/node_modules/@maka/core/dist/runtime-policy/connection-catalog-codec.js +4 -0
- package/node_modules/@maka/core/dist/session.js +17 -1
- package/node_modules/@maka/core/dist/settings.js +2 -0
- package/node_modules/@maka/core/package.json +1 -0
- package/node_modules/@maka/runtime/dist/ai-sdk-message-projection.js +15 -60
- package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +18 -0
- package/node_modules/@maka/runtime/dist/apply-patch-profile.js +64 -14
- package/node_modules/@maka/runtime/dist/builtin-tools.js +3 -2
- package/node_modules/@maka/runtime/dist/codex-v4a-patch.js +18 -0
- package/node_modules/@maka/runtime/dist/model-adapter.js +80 -3
- package/node_modules/@maka/runtime/dist/model-runtime.js +6 -0
- package/node_modules/@maka/runtime/dist/openai-apply-patch.js +5 -0
- package/node_modules/@maka/runtime/dist/plugin-executor-backend.js +6 -0
- package/node_modules/@maka/runtime/dist/session-manager.js +15 -2
- package/node_modules/@maka/runtime-host/dist/client/capability-provider-service.js +2 -0
- package/node_modules/@maka/runtime-host/dist/client/client-capability-channel.js +59 -20
- package/node_modules/@maka/runtime-host/dist/client/connection.js +6 -4
- package/node_modules/@maka/runtime-host/dist/client/reconnecting-connection.js +4 -4
- package/node_modules/@maka/runtime-host/dist/protocol/client-capability.js +20 -3
- package/node_modules/@maka/runtime-host/dist/protocol/external-session.js +8 -5
- package/node_modules/@maka/runtime-host/dist/protocol/index.js +11 -1
- package/node_modules/@maka/runtime-host/dist/protocol/session-catalog.js +35 -3
- package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +1 -5
- package/node_modules/@maka/runtime-host/dist/server/client-capability-coordinator.js +234 -70
- package/node_modules/@maka/runtime-host/dist/server/client-capability-invocation-broker.js +14 -8
- package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +29 -2
- package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +3 -0
- package/node_modules/@maka/runtime-host/dist/server/external-session-coordinator.js +29 -7
- package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +1 -1
- package/node_modules/@maka/runtime-host/dist/server/session-bundle-coordinator.js +4 -1
- package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +32 -4
- package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +2 -2
- package/package.json +1 -1
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
* under the License.
|
|
18
18
|
*/
|
|
19
19
|
import { openai } from '@ai-sdk/openai';
|
|
20
|
+
import { CODEX_PATCH_DESCRIPTION, CODEX_PATCH_GRAMMAR } from './codex-v4a-patch.js';
|
|
21
|
+
export const codexApplyPatchProviderTool = openai.tools.customTool({
|
|
22
|
+
description: `${CODEX_PATCH_DESCRIPTION}\nPass raw patch text, not JSON.`,
|
|
23
|
+
format: { type: 'grammar', syntax: 'lark', definition: CODEX_PATCH_GRAMMAR },
|
|
24
|
+
});
|
|
20
25
|
export const openAiApplyPatchProviderTool = openai.tools.applyPatch({});
|
|
21
26
|
const inputSchema = openAiApplyPatchProviderTool.inputSchema;
|
|
22
27
|
export const openAiApplyPatchInputSchema = typeof inputSchema === 'function' ? inputSchema() : inputSchema;
|
|
@@ -24,6 +24,8 @@ export class PluginExecutorBackend {
|
|
|
24
24
|
sessionId;
|
|
25
25
|
#cwd;
|
|
26
26
|
#instructions;
|
|
27
|
+
#model;
|
|
28
|
+
#thinkingLevel;
|
|
27
29
|
#binding;
|
|
28
30
|
#newId;
|
|
29
31
|
#now;
|
|
@@ -33,6 +35,8 @@ export class PluginExecutorBackend {
|
|
|
33
35
|
this.sessionId = input.sessionId;
|
|
34
36
|
this.#cwd = input.cwd;
|
|
35
37
|
this.#instructions = input.instructions;
|
|
38
|
+
this.#model = input.model;
|
|
39
|
+
this.#thinkingLevel = input.thinkingLevel;
|
|
36
40
|
this.#binding = input.binding;
|
|
37
41
|
this.#newId = input.newId ?? randomUUID;
|
|
38
42
|
this.#now = input.now ?? Date.now;
|
|
@@ -93,6 +97,8 @@ export class PluginExecutorBackend {
|
|
|
93
97
|
conversationKey: this.sessionId,
|
|
94
98
|
text: input.text,
|
|
95
99
|
cwd: this.#cwd,
|
|
100
|
+
...(this.#model ? { model: this.#model } : {}),
|
|
101
|
+
reasoningEffort: this.#thinkingLevel ?? null,
|
|
96
102
|
...(this.#instructions ? { instructions: this.#instructions } : {}),
|
|
97
103
|
...(input.attachments ? { attachments: input.attachments } : {}),
|
|
98
104
|
...(input.directoryReferences ? { directoryReferences: input.directoryReferences } : {}),
|
|
@@ -547,14 +547,27 @@ export class SessionManager {
|
|
|
547
547
|
async recoverInterruptedSessions() {
|
|
548
548
|
return this.recoverInterruptedSessionsWithPolicy({ kind: 'best_effort' });
|
|
549
549
|
}
|
|
550
|
+
/**
|
|
551
|
+
* Recover only the Sessions named by an already-held quiescent operation.
|
|
552
|
+
*
|
|
553
|
+
* Bundle export uses this after fencing a Session subtree. A persisted open
|
|
554
|
+
* invocation can survive a Host restart even though no live execution claim
|
|
555
|
+
* remains; settling that invocation is safe under the fence and lets export
|
|
556
|
+
* distinguish an interrupted run from a live one without scanning or
|
|
557
|
+
* mutating unrelated Sessions.
|
|
558
|
+
*/
|
|
559
|
+
async recoverInterruptedSessionsForSessions(sessionIds) {
|
|
560
|
+
const scope = new Set(sessionIds);
|
|
561
|
+
return this.recoverInterruptedSessionsWithPolicy({ kind: 'best_effort' }, scope);
|
|
562
|
+
}
|
|
550
563
|
async recoverInterruptedSessionsStrict(stores) {
|
|
551
564
|
if (stores.sessionStore !== this.deps.store || stores.agentRunStore !== this.deps.runStore) {
|
|
552
565
|
throw new Error('Strict recovery stores must match the SessionManager composition');
|
|
553
566
|
}
|
|
554
567
|
return this.recoverInterruptedSessionsWithPolicy({ kind: 'strict', stores });
|
|
555
568
|
}
|
|
556
|
-
async recoverInterruptedSessionsWithPolicy(policy) {
|
|
557
|
-
const interrupted = (await listSessionsForRecovery(this.deps.store, policy)).filter((session) => !session.isArchived);
|
|
569
|
+
async recoverInterruptedSessionsWithPolicy(policy, scope) {
|
|
570
|
+
const interrupted = (await listSessionsForRecovery(this.deps.store, policy)).filter((session) => !session.isArchived && (scope === undefined || scope.has(session.id)));
|
|
558
571
|
const recovered = new Set();
|
|
559
572
|
for (const session of interrupted) {
|
|
560
573
|
if (this.runtimeKernel.hasActiveRuns(session.id))
|
|
@@ -140,12 +140,14 @@ function snapshotProvider(provider) {
|
|
|
140
140
|
const call = provider.call?.bind(provider);
|
|
141
141
|
const callService = provider.callService?.bind(provider);
|
|
142
142
|
const close = provider.close?.bind(provider);
|
|
143
|
+
const currentRegistrationRetired = provider.currentRegistrationRetired?.bind(provider);
|
|
143
144
|
return {
|
|
144
145
|
offers: () => canonical.offers,
|
|
145
146
|
...(canonical.services === undefined ? {} : { services: () => canonical.services ?? [] }),
|
|
146
147
|
...(call ? { call } : {}),
|
|
147
148
|
...(callService ? { callService } : {}),
|
|
148
149
|
...(close ? { close } : {}),
|
|
150
|
+
...(currentRegistrationRetired ? { currentRegistrationRetired } : {}),
|
|
149
151
|
};
|
|
150
152
|
}
|
|
151
153
|
async function closeProvider(provider) {
|
|
@@ -24,74 +24,82 @@ export class ClientCapabilityChannel {
|
|
|
24
24
|
#registrations = new Map();
|
|
25
25
|
#invocations = new Map();
|
|
26
26
|
#releasedRegistrationIds = new Set();
|
|
27
|
-
#
|
|
28
|
-
#
|
|
27
|
+
#currentRegistrationIds = new Map();
|
|
28
|
+
#pendingMutations = new Set();
|
|
29
29
|
#closedError;
|
|
30
30
|
constructor(options) {
|
|
31
31
|
this.#options = options;
|
|
32
32
|
}
|
|
33
|
-
async replace(provider, timeoutMs) {
|
|
33
|
+
async replace(provider, timeoutMs, sessionId) {
|
|
34
34
|
this.#assertOpen();
|
|
35
|
-
if (this.#
|
|
35
|
+
if (this.#pendingMutations.has(sessionId)) {
|
|
36
36
|
throw new Error('A Client Capability registration mutation is already pending');
|
|
37
37
|
}
|
|
38
|
-
this.#
|
|
38
|
+
this.#pendingMutations.add(sessionId);
|
|
39
39
|
const registrationId = randomUUID();
|
|
40
40
|
let registration;
|
|
41
41
|
try {
|
|
42
42
|
const services = provider.services?.() ?? [];
|
|
43
43
|
const canonical = decodeClientCapabilityReplaceInput({
|
|
44
44
|
registrationId,
|
|
45
|
+
...(sessionId === undefined ? {} : { sessionId }),
|
|
45
46
|
offers: provider.offers(),
|
|
46
47
|
...(services.length === 0 ? {} : { services }),
|
|
47
48
|
});
|
|
48
49
|
registration = {
|
|
49
50
|
registrationId,
|
|
51
|
+
...(canonical.sessionId === undefined ? {} : { sessionId: canonical.sessionId }),
|
|
50
52
|
provider,
|
|
51
53
|
offers: canonical.offers,
|
|
52
54
|
services: canonical.services ?? [],
|
|
53
55
|
};
|
|
54
56
|
this.#registrations.set(registrationId, registration);
|
|
55
57
|
const result = await this.#options.replace(canonical, timeoutMs);
|
|
58
|
+
this.#assertOpen();
|
|
56
59
|
if (result.registrationId !== registrationId) {
|
|
57
60
|
throw new Error('Runtime Host replaced a different Client Capability registration');
|
|
58
61
|
}
|
|
59
|
-
this.#
|
|
62
|
+
this.#currentRegistrationIds.set(sessionId, registrationId);
|
|
60
63
|
this.#collectReleasedRegistrations();
|
|
61
64
|
return result;
|
|
62
65
|
}
|
|
63
66
|
catch (error) {
|
|
64
|
-
if (registration && this.#
|
|
67
|
+
if (registration && this.#currentRegistrationIds.get(sessionId) !== registrationId) {
|
|
65
68
|
this.#registrations.delete(registrationId);
|
|
66
69
|
}
|
|
67
70
|
throw error;
|
|
68
71
|
}
|
|
69
72
|
finally {
|
|
70
|
-
this.#
|
|
73
|
+
this.#pendingMutations.delete(sessionId);
|
|
74
|
+
this.#settleReleasedCurrentRegistration(sessionId);
|
|
75
|
+
this.#collectReleasedRegistrations();
|
|
71
76
|
}
|
|
72
77
|
}
|
|
73
|
-
async unregister(timeoutMs) {
|
|
78
|
+
async unregister(timeoutMs, sessionId) {
|
|
74
79
|
this.#assertOpen();
|
|
75
|
-
if (this.#
|
|
80
|
+
if (this.#pendingMutations.has(sessionId)) {
|
|
76
81
|
throw new Error('A Client Capability registration mutation is already pending');
|
|
77
82
|
}
|
|
78
|
-
const registrationId = this.#
|
|
83
|
+
const registrationId = this.#currentRegistrationIds.get(sessionId);
|
|
79
84
|
if (!registrationId)
|
|
80
85
|
throw new Error('No Client Capability registration is active');
|
|
81
|
-
this.#
|
|
86
|
+
this.#pendingMutations.add(sessionId);
|
|
82
87
|
try {
|
|
83
88
|
const result = await this.#options.unregister({ registrationId }, timeoutMs);
|
|
89
|
+
this.#assertOpen();
|
|
84
90
|
if (result.registrationId !== registrationId) {
|
|
85
91
|
throw new Error('Runtime Host unregistered a different Client Capability registration');
|
|
86
92
|
}
|
|
87
|
-
if (this.#
|
|
88
|
-
this.#
|
|
93
|
+
if (this.#currentRegistrationIds.get(sessionId) === registrationId) {
|
|
94
|
+
this.#currentRegistrationIds.delete(sessionId);
|
|
89
95
|
}
|
|
90
96
|
this.#collectReleasedRegistrations();
|
|
91
97
|
return result;
|
|
92
98
|
}
|
|
93
99
|
finally {
|
|
94
|
-
this.#
|
|
100
|
+
this.#pendingMutations.delete(sessionId);
|
|
101
|
+
this.#settleReleasedCurrentRegistration(sessionId);
|
|
102
|
+
this.#collectReleasedRegistrations();
|
|
95
103
|
}
|
|
96
104
|
}
|
|
97
105
|
accept(frame) {
|
|
@@ -124,10 +132,17 @@ export class ClientCapabilityChannel {
|
|
|
124
132
|
this.#invocations.delete(frame.invocationId);
|
|
125
133
|
return;
|
|
126
134
|
}
|
|
127
|
-
case 'client.capability.registration_release':
|
|
135
|
+
case 'client.capability.registration_release': {
|
|
136
|
+
const registration = this.#registrations.get(frame.registrationId);
|
|
128
137
|
this.#releasedRegistrationIds.add(frame.registrationId);
|
|
138
|
+
// A successful replacement can enqueue the old release before its response. Defer
|
|
139
|
+
// current-slot retirement until the in-flight mutation establishes which ID won.
|
|
140
|
+
if (registration && !this.#pendingMutations.has(registration.sessionId)) {
|
|
141
|
+
this.#settleReleasedCurrentRegistration(registration.sessionId);
|
|
142
|
+
}
|
|
129
143
|
this.#collectReleasedRegistrations();
|
|
130
144
|
return;
|
|
145
|
+
}
|
|
131
146
|
case 'client.capability.admitted': {
|
|
132
147
|
const invocation = this.#invocations.get(frame.invocationId);
|
|
133
148
|
if (!invocation?.admission || !invocation.admission.resolve()) {
|
|
@@ -161,7 +176,7 @@ export class ClientCapabilityChannel {
|
|
|
161
176
|
const providers = new Set([...this.#registrations.values()].map((registration) => registration.provider));
|
|
162
177
|
this.#registrations.clear();
|
|
163
178
|
this.#releasedRegistrationIds.clear();
|
|
164
|
-
this.#
|
|
179
|
+
this.#currentRegistrationIds.clear();
|
|
165
180
|
for (const provider of providers)
|
|
166
181
|
this.#closeProvider(provider);
|
|
167
182
|
}
|
|
@@ -172,7 +187,11 @@ export class ClientCapabilityChannel {
|
|
|
172
187
|
const registration = this.#registrations.get(frame.registrationId);
|
|
173
188
|
const offer = registration?.offers.find((candidate) => candidate.offerId === frame.offerId);
|
|
174
189
|
const offered = offer?.tools.some((tool) => tool.serverId === frame.serverId && tool.name === frame.toolName);
|
|
175
|
-
if (!registration ||
|
|
190
|
+
if (!registration ||
|
|
191
|
+
(registration.sessionId !== undefined && registration.sessionId !== frame.sessionId) ||
|
|
192
|
+
!offer ||
|
|
193
|
+
!offered ||
|
|
194
|
+
!registration.provider.call) {
|
|
176
195
|
void this.#options
|
|
177
196
|
.write({
|
|
178
197
|
kind: 'client.capability.rejected',
|
|
@@ -403,10 +422,12 @@ export class ClientCapabilityChannel {
|
|
|
403
422
|
}
|
|
404
423
|
#collectReleasedRegistrations() {
|
|
405
424
|
for (const registrationId of this.#releasedRegistrationIds) {
|
|
406
|
-
if (
|
|
425
|
+
if ([...this.#currentRegistrationIds.values()].includes(registrationId))
|
|
407
426
|
continue;
|
|
408
|
-
this.#releasedRegistrationIds.delete(registrationId);
|
|
409
427
|
const registration = this.#registrations.get(registrationId);
|
|
428
|
+
if (registration && this.#pendingMutations.has(registration.sessionId))
|
|
429
|
+
continue;
|
|
430
|
+
this.#releasedRegistrationIds.delete(registrationId);
|
|
410
431
|
if (!registration)
|
|
411
432
|
continue;
|
|
412
433
|
this.#registrations.delete(registrationId);
|
|
@@ -415,6 +436,16 @@ export class ClientCapabilityChannel {
|
|
|
415
436
|
}
|
|
416
437
|
}
|
|
417
438
|
}
|
|
439
|
+
#settleReleasedCurrentRegistration(sessionId) {
|
|
440
|
+
const registrationId = this.#currentRegistrationIds.get(sessionId);
|
|
441
|
+
if (!registrationId || !this.#releasedRegistrationIds.has(registrationId))
|
|
442
|
+
return;
|
|
443
|
+
const registration = this.#registrations.get(registrationId);
|
|
444
|
+
this.#currentRegistrationIds.delete(sessionId);
|
|
445
|
+
this.#collectReleasedRegistrations();
|
|
446
|
+
if (registration)
|
|
447
|
+
this.#notifyCurrentRegistrationRetired(registration.provider);
|
|
448
|
+
}
|
|
418
449
|
#closeProvider(provider) {
|
|
419
450
|
try {
|
|
420
451
|
void Promise.resolve(provider.close?.()).catch((error) => this.#options.onFailure(asError(error)));
|
|
@@ -423,6 +454,14 @@ export class ClientCapabilityChannel {
|
|
|
423
454
|
this.#options.onFailure(asError(error));
|
|
424
455
|
}
|
|
425
456
|
}
|
|
457
|
+
#notifyCurrentRegistrationRetired(provider) {
|
|
458
|
+
try {
|
|
459
|
+
void Promise.resolve(provider.currentRegistrationRetired?.()).catch((error) => this.#options.onFailure(asError(error)));
|
|
460
|
+
}
|
|
461
|
+
catch (error) {
|
|
462
|
+
this.#options.onFailure(asError(error));
|
|
463
|
+
}
|
|
464
|
+
}
|
|
426
465
|
#assertOpen() {
|
|
427
466
|
if (this.#closedError)
|
|
428
467
|
throw this.#closedError;
|
|
@@ -299,11 +299,13 @@ class RuntimeHostConnectionImpl {
|
|
|
299
299
|
this.#fail(new RuntimeHostTransportError('closed', 'Runtime Host connection closed by Client'), true);
|
|
300
300
|
await this.closed;
|
|
301
301
|
}
|
|
302
|
-
async replaceClientCapabilities(provider,
|
|
303
|
-
|
|
302
|
+
async replaceClientCapabilities(provider, options) {
|
|
303
|
+
const { timeoutMs = DEFAULT_HANDSHAKE_TIMEOUT_MS, sessionId } = typeof options === 'number' ? { timeoutMs: options } : (options ?? {});
|
|
304
|
+
return this.#clientCapabilities.replace(provider, timeoutMs, sessionId);
|
|
304
305
|
}
|
|
305
|
-
async unregisterClientCapabilities(
|
|
306
|
-
|
|
306
|
+
async unregisterClientCapabilities(options) {
|
|
307
|
+
const { timeoutMs = DEFAULT_HANDSHAKE_TIMEOUT_MS, sessionId } = typeof options === 'number' ? { timeoutMs: options } : (options ?? {});
|
|
308
|
+
return this.#clientCapabilities.unregister(timeoutMs, sessionId);
|
|
307
309
|
}
|
|
308
310
|
subscribeConfigurationChanges(listener) {
|
|
309
311
|
this.#configurationChangeListeners.add(listener);
|
|
@@ -142,13 +142,13 @@ class RuntimeHostReconnectingConnectionImpl {
|
|
|
142
142
|
openSessionSubscriptionOnce(input, timeoutMs) {
|
|
143
143
|
return this.#requireCurrent('subscription.open').openSessionSubscription(input, timeoutMs);
|
|
144
144
|
}
|
|
145
|
-
async replaceClientCapabilities(provider,
|
|
145
|
+
async replaceClientCapabilities(provider, options) {
|
|
146
146
|
const connection = this.#requireCurrent('client.capability.replace');
|
|
147
|
-
return connection.replaceClientCapabilities(provider,
|
|
147
|
+
return connection.replaceClientCapabilities(provider, options);
|
|
148
148
|
}
|
|
149
|
-
async unregisterClientCapabilities(
|
|
149
|
+
async unregisterClientCapabilities(options) {
|
|
150
150
|
const connection = this.#requireCurrent('client.capability.unregister');
|
|
151
|
-
return connection.unregisterClientCapabilities(
|
|
151
|
+
return connection.unregisterClientCapabilities(options);
|
|
152
152
|
}
|
|
153
153
|
subscribeConfigurationChanges(listener) {
|
|
154
154
|
this.#configurationListeners.add(listener);
|
|
@@ -59,7 +59,7 @@ export const CLIENT_CAPABILITY_OPERATION_SPECS = {
|
|
|
59
59
|
};
|
|
60
60
|
export function decodeClientCapabilityReplaceInput(value) {
|
|
61
61
|
const record = requireRecord(value, 'Client Capability replacement');
|
|
62
|
-
assertOptionalExactKeys(record, 'Client Capability replacement', ['registrationId', 'offers'], ['services']);
|
|
62
|
+
assertOptionalExactKeys(record, 'Client Capability replacement', ['registrationId', 'offers'], ['services', 'sessionId']);
|
|
63
63
|
if (!Array.isArray(record.offers) || record.offers.length > CLIENT_CAPABILITY_MAX_OFFERS) {
|
|
64
64
|
throw invalidProtocolFrame('Invalid Client Capability offers');
|
|
65
65
|
}
|
|
@@ -67,11 +67,23 @@ export function decodeClientCapabilityReplaceInput(value) {
|
|
|
67
67
|
if (!Array.isArray(serviceValues) || serviceValues.length > CLIENT_CAPABILITY_MAX_SERVICES) {
|
|
68
68
|
throw invalidProtocolFrame('Invalid Client Capability services');
|
|
69
69
|
}
|
|
70
|
-
|
|
70
|
+
// An empty Session snapshot clears lost contracts after reconnect and keeps
|
|
71
|
+
// a bounded registration through which the Host can signal Session retirement.
|
|
72
|
+
// Connection-wide publications still use unregister for withdrawal.
|
|
73
|
+
if (record.offers.length === 0 && serviceValues.length === 0 && record.sessionId === undefined) {
|
|
71
74
|
throw invalidProtocolFrame('Client Capability registration is empty');
|
|
72
75
|
}
|
|
73
76
|
const offers = record.offers.map((offer) => decodeClientCapabilityOffer(offer));
|
|
74
77
|
const services = serviceValues.map((service) => decodeClientCapabilityServiceOffer(service));
|
|
78
|
+
const sessionId = record.sessionId === undefined ? undefined : requireEntityId(record.sessionId, 'sessionId');
|
|
79
|
+
if (sessionId !== undefined &&
|
|
80
|
+
(services.length > 0 ||
|
|
81
|
+
offers.some((offer) => offer.affinity !== 'session' || offer.hostPathAccess !== 'none'))) {
|
|
82
|
+
throw invalidProtocolFrame('Session capabilities must be path-independent session tools');
|
|
83
|
+
}
|
|
84
|
+
if (offers.some((offer) => offer.admission === 'mcp') && sessionId === undefined) {
|
|
85
|
+
throw invalidProtocolFrame('MCP admission requires a target Session');
|
|
86
|
+
}
|
|
75
87
|
const offerIds = new Set();
|
|
76
88
|
const serviceContracts = new Set();
|
|
77
89
|
const toolIdentities = new Set();
|
|
@@ -102,6 +114,7 @@ export function decodeClientCapabilityReplaceInput(value) {
|
|
|
102
114
|
}
|
|
103
115
|
const decoded = {
|
|
104
116
|
registrationId: requireEntityId(record.registrationId, 'registrationId'),
|
|
117
|
+
...(sessionId === undefined ? {} : { sessionId }),
|
|
105
118
|
offers,
|
|
106
119
|
...(record.services === undefined ? {} : { services }),
|
|
107
120
|
};
|
|
@@ -427,17 +440,21 @@ function decodeClientCapabilityFormResult(value) {
|
|
|
427
440
|
}
|
|
428
441
|
function decodeClientCapabilityOffer(value) {
|
|
429
442
|
const record = requireRecord(value, 'Client Capability offer');
|
|
430
|
-
assertOptionalExactKeys(record, 'Client Capability offer', ['offerId', 'version', 'affinity', 'hostPathAccess', 'label', 'tools'], ['description']);
|
|
443
|
+
assertOptionalExactKeys(record, 'Client Capability offer', ['offerId', 'version', 'affinity', 'hostPathAccess', 'label', 'tools'], ['description', 'admission']);
|
|
431
444
|
if (!Array.isArray(record.tools) ||
|
|
432
445
|
record.tools.length === 0 ||
|
|
433
446
|
record.tools.length > CLIENT_CAPABILITY_MAX_TOOLS_PER_OFFER) {
|
|
434
447
|
throw invalidProtocolFrame('Invalid Client Capability offer tools');
|
|
435
448
|
}
|
|
449
|
+
if (record.admission !== undefined && record.admission !== 'mcp') {
|
|
450
|
+
throw invalidProtocolFrame('Invalid Client Capability admission');
|
|
451
|
+
}
|
|
436
452
|
return {
|
|
437
453
|
offerId: requireEntityId(record.offerId, 'offerId'),
|
|
438
454
|
version: requireString(record.version, 'version', 64),
|
|
439
455
|
affinity: decodeClientCapabilityAffinity(record.affinity),
|
|
440
456
|
hostPathAccess: decodeClientCapabilityHostPathAccess(record.hostPathAccess),
|
|
457
|
+
...(record.admission === 'mcp' ? { admission: 'mcp' } : {}),
|
|
441
458
|
label: requireString(record.label, 'label', 128),
|
|
442
459
|
...(record.description === undefined
|
|
443
460
|
? {}
|
|
@@ -120,15 +120,18 @@ export function decodeExternalSessionCatalogQueryResult(value) {
|
|
|
120
120
|
return decoded;
|
|
121
121
|
}
|
|
122
122
|
export function decodeExternalSessionImportInput(value) {
|
|
123
|
-
const input =
|
|
124
|
-
'adapterId',
|
|
125
|
-
'sourceSessionId',
|
|
126
|
-
]);
|
|
123
|
+
const input = requireShapedRecord(value, 'external Session import input', ['adapterId', 'sourceSessionId'], ['workspace']);
|
|
127
124
|
const sourceSessionId = requireUtf8String(input.sourceSessionId, 'external source Session id', EXTERNAL_SESSION_SOURCE_SESSION_ID_MAX_BYTES);
|
|
128
125
|
if (/[\u0000-\u001f\u007f]/.test(sourceSessionId)) {
|
|
129
126
|
throw invalidProtocolFrame('Invalid external source Session id');
|
|
130
127
|
}
|
|
131
|
-
return {
|
|
128
|
+
return {
|
|
129
|
+
adapterId: adapterId(input.adapterId),
|
|
130
|
+
sourceSessionId,
|
|
131
|
+
...(Object.hasOwn(input, 'workspace')
|
|
132
|
+
? { workspace: decodeWorkspaceTarget(input.workspace) }
|
|
133
|
+
: {}),
|
|
134
|
+
};
|
|
132
135
|
}
|
|
133
136
|
export function decodeExternalSessionImportResult(value) {
|
|
134
137
|
const result = requireRecord(value, 'external Session import result');
|
|
@@ -62,7 +62,17 @@ export const RUNTIME_HOST_REGISTRATION_SCHEMA_VERSION = 1;
|
|
|
62
62
|
export const RUNTIME_HOST_PROTOCOL_VERSION = 0;
|
|
63
63
|
// Increment when the same protocol version no longer guarantees safe Client-Host
|
|
64
64
|
// interoperability. Mismatches are rejected before domain commands are admitted.
|
|
65
|
-
export const RUNTIME_HOST_COMPATIBILITY_EPOCH =
|
|
65
|
+
export const RUNTIME_HOST_COMPATIBILITY_EPOCH = 177;
|
|
66
|
+
// 177: External Session import input may carry an optional Host-resolved
|
|
67
|
+
// workspace target. Epoch-176 peers reject the unknown `workspace` key.
|
|
68
|
+
// 176: Session-scoped capability publication and MCP admission require compatible
|
|
69
|
+
// Client and Host builds; older peers do not enforce their isolation contract.
|
|
70
|
+
// 175: WorkHub coordinator model configuration again accepts only native
|
|
71
|
+
// explicit targets. Epoch-174 peers may send an executor target it must reject.
|
|
72
|
+
// 174: WorkHub new-Work defaults may select an executor-specific model and
|
|
73
|
+
// thinking level. Epoch-173 peers reject these fields on strict action shapes.
|
|
74
|
+
// 173: Plugin executor Sessions may select an executor-specific model at
|
|
75
|
+
// creation and configuration time, and propagate its reasoning effort.
|
|
66
76
|
// 172: Usage screen requests and results accept fractional timestamps in the
|
|
67
77
|
// persisted domain. Older peers reject these otherwise valid wire values.
|
|
68
78
|
// 171: Removed the `connection.usage.read` operation along with the Command
|
|
@@ -20,7 +20,7 @@ import { isCollaborationMode } from '@maka/core/collaboration';
|
|
|
20
20
|
import { isOrchestrationMode } from '@maka/core/orchestration';
|
|
21
21
|
import { isPermissionMode } from '@maka/core/permission';
|
|
22
22
|
import { isSessionStartMode } from '@maka/core/session-start-mode';
|
|
23
|
-
import { isSessionBlockedReason, isSessionToolProfile, } from '@maka/core/session';
|
|
23
|
+
import { isSessionBlockedReason, isSessionToolProfile, SESSION_MODEL_ID_MAX_BYTES, } from '@maka/core/session';
|
|
24
24
|
import { isThinkingLevel } from '@maka/core/model-thinking';
|
|
25
25
|
import { isExecutorId } from '@maka/core/executor-id';
|
|
26
26
|
import { assertAllowedKeys, requireCount, requireEncodedByteLimit, requireEntityId, requireExactRecord, requireRecord, requireShapedRecord, requireUtf8String, } from './codec.js';
|
|
@@ -36,7 +36,7 @@ export const SESSION_CATALOG_NAME_MAX_BYTES = 320;
|
|
|
36
36
|
export const SESSION_CATALOG_LABEL_MAX_ITEMS = 32;
|
|
37
37
|
export const SESSION_CATALOG_LABEL_MAX_BYTES = 128;
|
|
38
38
|
export const SESSION_CATALOG_PREVIEW_MAX_BYTES = 4 * 1024;
|
|
39
|
-
export const SESSION_CATALOG_MODEL_MAX_BYTES =
|
|
39
|
+
export const SESSION_CATALOG_MODEL_MAX_BYTES = SESSION_MODEL_ID_MAX_BYTES;
|
|
40
40
|
export const SESSION_CATALOG_CONNECTION_SLUG_MAX_BYTES = 256;
|
|
41
41
|
export const SESSION_CATALOG_LIVE_RUN_STATE_SCHEMA_VERSION = 1;
|
|
42
42
|
export const SESSION_CATALOG_RUNNING_TURN_MAX_ITEMS = 64;
|
|
@@ -251,6 +251,7 @@ export function decodeSessionCreateInput(value) {
|
|
|
251
251
|
'labels',
|
|
252
252
|
'modelTarget',
|
|
253
253
|
'executorId',
|
|
254
|
+
'executorModel',
|
|
254
255
|
'thinkingLevel',
|
|
255
256
|
'toolProfile',
|
|
256
257
|
'permissionMode',
|
|
@@ -261,9 +262,15 @@ export function decodeSessionCreateInput(value) {
|
|
|
261
262
|
? executorIdValue(input.executorId)
|
|
262
263
|
: undefined;
|
|
263
264
|
const target = Object.hasOwn(input, 'modelTarget') ? modelTarget(input.modelTarget) : undefined;
|
|
265
|
+
const executorModel = Object.hasOwn(input, 'executorModel')
|
|
266
|
+
? requireUtf8String(input.executorModel, 'Executor model', SESSION_CATALOG_MODEL_MAX_BYTES)
|
|
267
|
+
: undefined;
|
|
264
268
|
if ((executorId === undefined) === (target === undefined)) {
|
|
265
269
|
throw invalidProtocolFrame('Session creation requires exactly one model target or executor id');
|
|
266
270
|
}
|
|
271
|
+
if (executorModel !== undefined && executorId === undefined) {
|
|
272
|
+
throw invalidProtocolFrame('Executor model requires an executor id');
|
|
273
|
+
}
|
|
267
274
|
return {
|
|
268
275
|
sessionId: requireEntityId(input.sessionId, 'sessionId'),
|
|
269
276
|
workspace: decodeWorkspaceTarget(input.workspace),
|
|
@@ -272,6 +279,7 @@ export function decodeSessionCreateInput(value) {
|
|
|
272
279
|
...(Object.hasOwn(input, 'labels') ? { labels: labels(input.labels) } : {}),
|
|
273
280
|
...(target ? { modelTarget: target } : {}),
|
|
274
281
|
...(executorId ? { executorId } : {}),
|
|
282
|
+
...(executorModel ? { executorModel } : {}),
|
|
275
283
|
...(Object.hasOwn(input, 'thinkingLevel')
|
|
276
284
|
? { thinkingLevel: input.thinkingLevel === null ? null : thinkingLevel(input.thinkingLevel) }
|
|
277
285
|
: {}),
|
|
@@ -327,10 +335,20 @@ export function decodeSessionConfigurationUpdateInput(value) {
|
|
|
327
335
|
'expectedRevision',
|
|
328
336
|
'patch',
|
|
329
337
|
]);
|
|
330
|
-
const patch = requireShapedRecord(input.patch, 'Session configuration patch', [], [
|
|
338
|
+
const patch = requireShapedRecord(input.patch, 'Session configuration patch', [], [
|
|
339
|
+
'modelTarget',
|
|
340
|
+
'executorTarget',
|
|
341
|
+
'thinkingLevel',
|
|
342
|
+
'permissionMode',
|
|
343
|
+
'collaborationMode',
|
|
344
|
+
'orchestrationMode',
|
|
345
|
+
]);
|
|
331
346
|
if (Object.keys(patch).length === 0) {
|
|
332
347
|
throw invalidProtocolFrame('Session configuration patch is empty');
|
|
333
348
|
}
|
|
349
|
+
if (Object.hasOwn(patch, 'modelTarget') && Object.hasOwn(patch, 'executorTarget')) {
|
|
350
|
+
throw invalidProtocolFrame('Session configuration cannot select two execution targets');
|
|
351
|
+
}
|
|
334
352
|
return {
|
|
335
353
|
sessionId: requireEntityId(input.sessionId, 'sessionId'),
|
|
336
354
|
expectedRevision: positiveRevision(input.expectedRevision, 'expected Session revision'),
|
|
@@ -338,6 +356,9 @@ export function decodeSessionConfigurationUpdateInput(value) {
|
|
|
338
356
|
...(Object.hasOwn(patch, 'modelTarget')
|
|
339
357
|
? { modelTarget: explicitModelTarget(patch.modelTarget) }
|
|
340
358
|
: {}),
|
|
359
|
+
...(Object.hasOwn(patch, 'executorTarget')
|
|
360
|
+
? { executorTarget: executorTarget(patch.executorTarget) }
|
|
361
|
+
: {}),
|
|
341
362
|
...(Object.hasOwn(patch, 'thinkingLevel')
|
|
342
363
|
? {
|
|
343
364
|
thinkingLevel: patch.thinkingLevel === null ? null : thinkingLevel(patch.thinkingLevel),
|
|
@@ -659,6 +680,17 @@ function executorIdValue(value) {
|
|
|
659
680
|
}
|
|
660
681
|
return id;
|
|
661
682
|
}
|
|
683
|
+
function executorTarget(value) {
|
|
684
|
+
const exact = requireShapedRecord(value, 'Session executor target', ['executorId'], ['model']);
|
|
685
|
+
return {
|
|
686
|
+
executorId: executorIdValue(exact.executorId),
|
|
687
|
+
...(Object.hasOwn(exact, 'model')
|
|
688
|
+
? {
|
|
689
|
+
model: requireUtf8String(exact.model, 'Executor model', SESSION_CATALOG_MODEL_MAX_BYTES),
|
|
690
|
+
}
|
|
691
|
+
: {}),
|
|
692
|
+
};
|
|
693
|
+
}
|
|
662
694
|
function optionalExecutorId(record) {
|
|
663
695
|
if (record.executorId === undefined)
|
|
664
696
|
return {};
|
|
@@ -25,11 +25,7 @@ import { defineOperation } from './operation-spec.js';
|
|
|
25
25
|
import { decodeWorkspaceProjection, decodeWorkspaceTarget, } from './workspace.js';
|
|
26
26
|
import { decodeSessionConfigurationUpdateInput, decodeSessionUpdateResult, decodeSessionCatalogItem, SESSION_CATALOG_OPERATION_SPECS, } from './session-catalog.js';
|
|
27
27
|
export function decodeWorkHubCoordinationConfigureModelInput(value) {
|
|
28
|
-
const input =
|
|
29
|
-
'expectedRevision',
|
|
30
|
-
'modelTarget',
|
|
31
|
-
'thinkingLevel',
|
|
32
|
-
]);
|
|
28
|
+
const input = requireShapedRecord(value, 'WorkHub model configuration', ['expectedRevision', 'modelTarget', 'thinkingLevel'], []);
|
|
33
29
|
const decoded = decodeSessionConfigurationUpdateInput({
|
|
34
30
|
sessionId: WORKHUB_COORDINATION_SESSION_ID,
|
|
35
31
|
expectedRevision: input.expectedRevision,
|