maka-agent 0.2.0-dev.47.20260922 → 0.2.0-dev.48.20260923
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/runtime-host-capability-provider-command.js +25 -6
- package/dist/tui-mcp-control.js +37 -24
- package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
- package/node_modules/@maka/acp-executor-plugin/dist/acp-errors.js +26 -0
- package/node_modules/@maka/acp-executor-plugin/dist/acp-filesystem.js +103 -0
- package/node_modules/@maka/acp-executor-plugin/dist/acp-process.js +119 -0
- package/node_modules/@maka/acp-executor-plugin/dist/acp-projection.js +146 -0
- package/node_modules/@maka/acp-executor-plugin/dist/index.js +653 -0
- package/node_modules/@maka/acp-executor-plugin/dist/plugin.mjs +24446 -0
- package/node_modules/@maka/acp-executor-plugin/package.json +15 -0
- package/node_modules/@maka/antigravity-acp-plugin/dist/index.js +88 -0
- package/node_modules/@maka/antigravity-acp-plugin/dist/model-presentation.js +53 -0
- package/node_modules/@maka/antigravity-acp-plugin/dist/plugin.mjs +115 -0
- package/node_modules/@maka/antigravity-acp-plugin/package.json +15 -0
- package/node_modules/@maka/core/dist/executor-catalog.js +106 -0
- package/node_modules/@maka/core/dist/mcp.js +4 -1
- package/node_modules/@maka/core/dist/model-metadata.js +28 -1
- package/node_modules/@maka/core/dist/runtime-event-store.js +25 -0
- package/node_modules/@maka/core/dist/runtime-invocation.js +5 -0
- package/node_modules/@maka/core/dist/runtime-policy/credential-vault-codec.js +6 -0
- package/node_modules/@maka/core/dist/runtime-policy/policy-codec.js +21 -0
- package/node_modules/@maka/core/dist/session.js +2 -0
- package/node_modules/@maka/core/dist/settings.js +1 -0
- package/node_modules/@maka/core/dist/tool-ledger-scanner.js +365 -104
- package/node_modules/@maka/core/dist/turn-origin.js +14 -0
- package/node_modules/@maka/core/package.json +2 -0
- package/node_modules/@maka/mcp/dist/credential-coordinator.js +6 -1
- package/node_modules/@maka/mcp/dist/index.js +31 -5
- package/node_modules/@maka/mcp/dist/oauth.js +54 -6
- package/node_modules/@maka/runtime/dist/agent-run-inspect.js +1 -1
- package/node_modules/@maka/runtime/dist/conversation-copy.js +56 -1
- package/node_modules/@maka/runtime/dist/mcp-tools.js +9 -2
- package/node_modules/@maka/runtime/dist/plugin-executor-backend.js +116 -3
- package/node_modules/@maka/runtime/dist/plugin-executor-service.js +212 -16
- package/node_modules/@maka/runtime/dist/runtime-ledger-repair.js +2 -2
- package/node_modules/@maka/runtime/dist/session-event-runtime-mapper.js +3 -0
- package/node_modules/@maka/runtime/dist/session-manager.js +170 -125
- package/node_modules/@maka/runtime/dist/shell-run-manager.js +3 -0
- package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +29 -4
- package/node_modules/@maka/runtime-host/dist/candidate-entry.js +8 -5
- package/node_modules/@maka/runtime-host/dist/client/index.js +1 -1
- package/node_modules/@maka/runtime-host/dist/client/reconnect-lifecycle.js +8 -1
- package/node_modules/@maka/runtime-host/dist/client/session-catalog-summary.js +6 -1
- package/node_modules/@maka/runtime-host/dist/composition-identity.js +19 -0
- package/node_modules/@maka/runtime-host/dist/protocol/index.js +10 -2
- package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +34 -0
- package/node_modules/@maka/runtime-host/dist/protocol/session-catalog.js +40 -2
- package/node_modules/@maka/runtime-host/dist/server/builtin-external-agent-plugins.js +188 -0
- package/node_modules/@maka/runtime-host/dist/server/candidate.js +12 -7
- package/node_modules/@maka/runtime-host/dist/server/client-capability-recovery.js +44 -46
- package/node_modules/@maka/runtime-host/dist/server/context-coordinator.js +2 -0
- package/node_modules/@maka/runtime-host/dist/server/execution-composition-factory.js +1 -1
- package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +159 -19
- package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +3 -0
- package/node_modules/@maka/runtime-host/dist/server/extension-bundle.js +4 -0
- package/node_modules/@maka/runtime-host/dist/server/host-composition-source.js +47 -0
- package/node_modules/@maka/runtime-host/dist/server/host-composition.js +1 -29
- package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +2 -9
- package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +75 -9
- package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +4 -0
- package/node_modules/@maka/runtime-host/dist/server/interaction-coordinator.js +45 -0
- package/node_modules/@maka/runtime-host/dist/server/jev-routing-model.js +157 -0
- package/node_modules/@maka/runtime-host/dist/server/listener-set.js +2 -2
- package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +4 -0
- package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +11 -1
- package/node_modules/@maka/runtime-host/dist/server/process-lifecycle.js +1 -1
- package/node_modules/@maka/runtime-host/dist/server/process-termination-error.js +27 -0
- package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +91 -12
- package/node_modules/@maka/runtime-host/dist/server/runtime-policy-coordinator.js +2 -0
- package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +99 -12
- package/node_modules/@maka/runtime-host/dist/server/session-effect-coordinator.js +12 -6
- package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +33 -0
- package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +4 -0
- package/node_modules/@maka/runtime-host/dist/server/workhub-result-coordinator.js +173 -0
- package/node_modules/@maka/runtime-host/dist/server/workhub-result-runtime.js +293 -0
- package/node_modules/@maka/runtime-host/package.json +2 -0
- package/node_modules/@maka/storage/dist/agent-run-store-contract.js +8 -0
- package/node_modules/@maka/storage/dist/execution-stores.js +15 -3
- package/node_modules/@maka/storage/dist/mcp-config-store.js +91 -2
- package/node_modules/@maka/storage/dist/operational-state-store.js +15 -1
- package/node_modules/@maka/storage/dist/plan-authority.js +2 -0
- package/node_modules/@maka/storage/dist/plan-store.js +30 -0
- package/node_modules/@maka/storage/dist/runtime-event-persistence.js +1 -0
- package/node_modules/@maka/storage/dist/runtime-policy/credential-vault-document.js +4 -1
- package/node_modules/@maka/storage/dist/runtime-policy/policy-document.js +2 -0
- package/node_modules/@maka/storage/dist/session-store-values.js +16 -4
- package/node_modules/@maka/storage/dist/shell-run-authority.js +1 -0
- package/node_modules/@maka/storage/dist/shell-run-store.js +15 -0
- package/node_modules/@maka/storage/dist/sqlite-runtime-schema.js +74 -1
- package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +724 -84
- package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +16 -0
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"type": "module",
|
|
7
7
|
"sideEffects": false,
|
|
8
8
|
"exports": {
|
|
9
|
+
"./executor-catalog": "./dist/executor-catalog.js",
|
|
9
10
|
"./executor-id": "./dist/executor-id.js",
|
|
10
11
|
"./durable-tool-result-projection": "./dist/durable-tool-result-projection.js",
|
|
11
12
|
"./model-projection-transition": "./dist/model-projection-transition.js",
|
|
@@ -29,6 +30,7 @@
|
|
|
29
30
|
"./provider-retry-countdown": "./dist/provider-retry-countdown.js",
|
|
30
31
|
"./interaction": "./dist/interaction.js",
|
|
31
32
|
"./session": "./dist/session.js",
|
|
33
|
+
"./turn-origin": "./dist/turn-origin.js",
|
|
32
34
|
"./session-reference": "./dist/session-reference.js",
|
|
33
35
|
"./session-revisions": "./dist/session-revisions.js",
|
|
34
36
|
"./collaboration": "./dist/collaboration.js",
|
|
@@ -44,7 +44,10 @@ export class McpCredentialCoordinator {
|
|
|
44
44
|
*
|
|
45
45
|
* The optional signal fences an ABANDONED erase: a logout whose round
|
|
46
46
|
* timed out must not resume later, adopt whatever record a newer login
|
|
47
|
-
* just stored as its basis, and tombstone the fresh tokens.
|
|
47
|
+
* just stored as its basis, and tombstone the fresh tokens.
|
|
48
|
+
*
|
|
49
|
+
* `spare` must be decided here, on the lane's read: checked outside, a
|
|
50
|
+
* login could land between the check and the erase. */
|
|
48
51
|
async erase(serverId, options = {}) {
|
|
49
52
|
this.epochs.set(serverId, this.epoch(serverId) + 1);
|
|
50
53
|
await this.run(serverId, async () => {
|
|
@@ -54,6 +57,8 @@ export class McpCredentialCoordinator {
|
|
|
54
57
|
// storage read was in flight — committing past it would erase a
|
|
55
58
|
// record the caller no longer owns.
|
|
56
59
|
this.assertNotAbandoned(serverId, options.signal);
|
|
60
|
+
if (basis && options.spare?.(basis))
|
|
61
|
+
return;
|
|
57
62
|
// Absence still gets a tombstone: a cross-process flow that captured
|
|
58
63
|
// generation 0 on absence must not CAS its credentials back in after
|
|
59
64
|
// this revocation.
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* under the License.
|
|
18
18
|
*/
|
|
19
19
|
import { randomBytes } from 'node:crypto';
|
|
20
|
-
import { auth, Client, extractWWWAuthenticateParams, LATEST_PROTOCOL_VERSION, CLIENT_CAPABILITIES_META_KEY, isInputRequiredResult, SdkErrorCode, SdkHttpError, SSEClientTransport, StreamableHTTPClientTransport, } from '@modelcontextprotocol/client';
|
|
20
|
+
import { auth, validateAuthorizationResponseIssuer, Client, extractWWWAuthenticateParams, LATEST_PROTOCOL_VERSION, CLIENT_CAPABILITIES_META_KEY, isInputRequiredResult, SdkErrorCode, SdkHttpError, SSEClientTransport, StreamableHTTPClientTransport, } from '@modelcontextprotocol/client';
|
|
21
21
|
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
|
|
22
22
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
23
23
|
import { containsMcpFormState, prepareMcpForm } from './form-elicitation.js';
|
|
@@ -34,7 +34,7 @@ import { McpToolCallPreparer } from './tool-output-validation.js';
|
|
|
34
34
|
import { mapMcpToolProgress } from './tool-progress.js';
|
|
35
35
|
import { McpCredentialCoordinator } from './credential-coordinator.js';
|
|
36
36
|
import { assertTransportSecurity, urlProvenance, } from './transport-security.js';
|
|
37
|
-
import { McpAuthRequiredError, McpOAuthProvider, } from './oauth.js';
|
|
37
|
+
import { McpAuthRequiredError, McpOAuthProvider, authorizationCallbackError, mcpOAuthRecordBoundTo, } from './oauth.js';
|
|
38
38
|
export { formatMcpDiagnosticText } from './diagnostic-text.js';
|
|
39
39
|
export { McpToolCallError } from './tool-call-error.js';
|
|
40
40
|
export { createCredentialMcpOAuthStorage, } from './credential-oauth-storage.js';
|
|
@@ -259,7 +259,7 @@ export class McpClientManager {
|
|
|
259
259
|
: undefined;
|
|
260
260
|
if (owed) {
|
|
261
261
|
try {
|
|
262
|
-
await this.forgetAuthorization(serverId, owed);
|
|
262
|
+
await this.forgetAuthorization(serverId, owed, { successor: serverConfig });
|
|
263
263
|
}
|
|
264
264
|
catch (error) {
|
|
265
265
|
await this.blockForCredentialCleanup(serverId, current, owed, error);
|
|
@@ -1272,7 +1272,6 @@ export class McpClientManager {
|
|
|
1272
1272
|
}
|
|
1273
1273
|
}
|
|
1274
1274
|
async finishAuthorizationRound(serverId, callback, options = {}) {
|
|
1275
|
-
const authorizationCode = callback.code;
|
|
1276
1275
|
const { config } = this.requireRemoteEntry(serverId);
|
|
1277
1276
|
// The immediate read doubles as the flow's generation/version pin.
|
|
1278
1277
|
const storage = this.flowStorage(serverId, options.signal);
|
|
@@ -1298,6 +1297,25 @@ export class McpClientManager {
|
|
|
1298
1297
|
if (record?.pendingServerUrl !== config.url) {
|
|
1299
1298
|
throw new Error(`MCP server "${serverId}" changed its URL during authorization`);
|
|
1300
1299
|
}
|
|
1300
|
+
const metadata = record.discovery?.authorizationServerMetadata;
|
|
1301
|
+
const expectedIssuer = metadata?.issuer ?? record.discovery?.authorizationServerUrl;
|
|
1302
|
+
if (!expectedIssuer)
|
|
1303
|
+
throw new Error('OAuth callback has no recorded issuer');
|
|
1304
|
+
try {
|
|
1305
|
+
validateAuthorizationResponseIssuer({
|
|
1306
|
+
iss: callback.iss,
|
|
1307
|
+
expectedIssuer: String(expectedIssuer),
|
|
1308
|
+
issParameterSupported: metadata?.authorization_response_iss_parameter_supported === true,
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
catch {
|
|
1312
|
+
// SDK errors echo the untrusted iss parameter; it must not cross IPC.
|
|
1313
|
+
throw new Error('OAuth callback issuer validation failed');
|
|
1314
|
+
}
|
|
1315
|
+
if ('error' in callback) {
|
|
1316
|
+
throw authorizationCallbackError(callback.error);
|
|
1317
|
+
}
|
|
1318
|
+
const authorizationCode = callback.code;
|
|
1301
1319
|
const provider = new McpOAuthProvider({
|
|
1302
1320
|
serverId,
|
|
1303
1321
|
serverUrl: config.url,
|
|
@@ -1432,7 +1450,15 @@ export class McpClientManager {
|
|
|
1432
1450
|
return;
|
|
1433
1451
|
if (config && isMcpStdioConfig(config) && !(await this.coordinator.read(serverId)))
|
|
1434
1452
|
return;
|
|
1435
|
-
|
|
1453
|
+
const { successor, ...eraseOptions } = options;
|
|
1454
|
+
await this.coordinator.erase(serverId, {
|
|
1455
|
+
...eraseOptions,
|
|
1456
|
+
// Another process may have applied the same change and signed in first.
|
|
1457
|
+
...(successor &&
|
|
1458
|
+
!isMcpStdioConfig(successor) && {
|
|
1459
|
+
spare: (record) => mcpOAuthRecordBoundTo(record, successor.url, successor.oauth),
|
|
1460
|
+
}),
|
|
1461
|
+
});
|
|
1436
1462
|
}
|
|
1437
1463
|
/** Drops stored tokens and registration, returning the server to needs-auth. */
|
|
1438
1464
|
async clearAuthorization(serverId, options = {}) {
|
|
@@ -16,6 +16,25 @@
|
|
|
16
16
|
* specific language governing permissions and limitations
|
|
17
17
|
* under the License.
|
|
18
18
|
*/
|
|
19
|
+
import { createHash } from 'node:crypto';
|
|
20
|
+
const OAUTH_ERROR_CODES = new Set([
|
|
21
|
+
'invalid_request',
|
|
22
|
+
'unauthorized_client',
|
|
23
|
+
'access_denied',
|
|
24
|
+
'unsupported_response_type',
|
|
25
|
+
'invalid_scope',
|
|
26
|
+
'server_error',
|
|
27
|
+
'temporarily_unavailable',
|
|
28
|
+
'invalid_client',
|
|
29
|
+
'invalid_grant',
|
|
30
|
+
'unsupported_grant_type',
|
|
31
|
+
'interaction_required',
|
|
32
|
+
'login_required',
|
|
33
|
+
'consent_required',
|
|
34
|
+
]);
|
|
35
|
+
export function authorizationCallbackError(code) {
|
|
36
|
+
return new Error(`Authorization failed: ${OAUTH_ERROR_CODES.has(code) ? code : 'unknown_error'}`);
|
|
37
|
+
}
|
|
19
38
|
export function createMemoryMcpOAuthStorage() {
|
|
20
39
|
const records = new Map();
|
|
21
40
|
return {
|
|
@@ -31,6 +50,16 @@ export function createMemoryMcpOAuthStorage() {
|
|
|
31
50
|
},
|
|
32
51
|
};
|
|
33
52
|
}
|
|
53
|
+
function mcpOAuthClientConfigHash(config) {
|
|
54
|
+
if (!config?.clientId)
|
|
55
|
+
return undefined;
|
|
56
|
+
return createHash('sha256')
|
|
57
|
+
.update(JSON.stringify([config.issuer, config.clientId, config.clientSecret]))
|
|
58
|
+
.digest('hex');
|
|
59
|
+
}
|
|
60
|
+
export function mcpOAuthRecordBoundTo(record, serverUrl, config) {
|
|
61
|
+
return (record.serverUrl === serverUrl && record.clientConfigHash === mcpOAuthClientConfigHash(config));
|
|
62
|
+
}
|
|
34
63
|
/** Thrown (via the SDK's UnauthorizedError path) when a background connect
|
|
35
64
|
* would need the user in a browser. The manager maps it to `needs-auth`. */
|
|
36
65
|
export class McpAuthRequiredError extends Error {
|
|
@@ -64,6 +93,9 @@ export class McpOAuthProvider {
|
|
|
64
93
|
if (state)
|
|
65
94
|
this.state = () => state;
|
|
66
95
|
}
|
|
96
|
+
get clientConfigHash() {
|
|
97
|
+
return mcpOAuthClientConfigHash(this.options.config);
|
|
98
|
+
}
|
|
67
99
|
get redirectUrl() {
|
|
68
100
|
return this.options.interactive?.redirectUrl ?? BACKGROUND_REDIRECT_URL;
|
|
69
101
|
}
|
|
@@ -82,10 +114,17 @@ export class McpOAuthProvider {
|
|
|
82
114
|
: {}),
|
|
83
115
|
};
|
|
84
116
|
}
|
|
85
|
-
async clientInformation() {
|
|
117
|
+
async clientInformation(context) {
|
|
86
118
|
const configured = this.options.config;
|
|
87
119
|
if (configured?.clientId) {
|
|
120
|
+
if (!configured.issuer) {
|
|
121
|
+
throw new Error(`MCP server "${this.options.serverId}" requires oauth.issuer for its pre-registered client`);
|
|
122
|
+
}
|
|
123
|
+
if (context && context.issuer !== configured.issuer) {
|
|
124
|
+
throw new Error(`MCP server "${this.options.serverId}" discovered a different OAuth issuer; reconfigure its client credentials`);
|
|
125
|
+
}
|
|
88
126
|
return {
|
|
127
|
+
issuer: configured.issuer,
|
|
89
128
|
client_id: configured.clientId,
|
|
90
129
|
...(configured.clientSecret ? { client_secret: configured.clientSecret } : {}),
|
|
91
130
|
};
|
|
@@ -106,7 +145,13 @@ export class McpOAuthProvider {
|
|
|
106
145
|
});
|
|
107
146
|
}
|
|
108
147
|
async tokens() {
|
|
109
|
-
|
|
148
|
+
if (this.options.config?.clientId)
|
|
149
|
+
await this.clientInformation();
|
|
150
|
+
const record = await this.read();
|
|
151
|
+
const tokens = record.tokens;
|
|
152
|
+
if (this.options.config?.issuer && tokens?.issuer !== this.options.config.issuer)
|
|
153
|
+
return undefined;
|
|
154
|
+
return tokens;
|
|
110
155
|
}
|
|
111
156
|
async saveTokens(tokens) {
|
|
112
157
|
await this.mutate((record) => {
|
|
@@ -148,9 +193,7 @@ export class McpOAuthProvider {
|
|
|
148
193
|
// A dynamically registered client belongs to the authorization server
|
|
149
194
|
// that issued it. When discovery moves the resource to a DIFFERENT
|
|
150
195
|
// authorization server, carrying the old registration over would send
|
|
151
|
-
// one AS's client credentials (and any secret) to another.
|
|
152
|
-
// config-supplied clients are unaffected — they never live in the
|
|
153
|
-
// record.
|
|
196
|
+
// one AS's client credentials (and any secret) to another.
|
|
154
197
|
const previousIssuer = record.discovery?.authorizationServerUrl;
|
|
155
198
|
const nextIssuer = state.authorizationServerUrl;
|
|
156
199
|
if (previousIssuer && nextIssuer && `${previousIssuer}` !== `${nextIssuer}`) {
|
|
@@ -217,7 +260,8 @@ export class McpOAuthProvider {
|
|
|
217
260
|
record.pendingRedirectUrl ||
|
|
218
261
|
record.pendingServerUrl ||
|
|
219
262
|
record.pendingState;
|
|
220
|
-
return Boolean(boundable) &&
|
|
263
|
+
return (Boolean(boundable) &&
|
|
264
|
+
!mcpOAuthRecordBoundTo(record, this.options.serverUrl, this.options.config));
|
|
221
265
|
}
|
|
222
266
|
/** The same fail-closed binding rule as read(), applied to a mutation
|
|
223
267
|
* basis: a record bound elsewhere contributes NOTHING to the new write —
|
|
@@ -239,6 +283,10 @@ export class McpOAuthProvider {
|
|
|
239
283
|
const stamp = (record) => {
|
|
240
284
|
apply(record);
|
|
241
285
|
record.serverUrl = this.options.serverUrl;
|
|
286
|
+
if (this.clientConfigHash)
|
|
287
|
+
record.clientConfigHash = this.clientConfigHash;
|
|
288
|
+
else
|
|
289
|
+
delete record.clientConfigHash;
|
|
242
290
|
return record;
|
|
243
291
|
};
|
|
244
292
|
// The coordinator-provided storage view makes read-apply-write one
|
|
@@ -38,7 +38,7 @@ export async function inspectAgentRunReadModel(runStore, runtimeEventStore, opti
|
|
|
38
38
|
diagnostics.push(inspectDiagnostic(invocation, 'runtime_terminal_missing', 'runtime ledger has no complete terminal RuntimeEvent fact'));
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
-
const projection = runtimeEvents.length > 0
|
|
41
|
+
const projection = runtimeEvents.length > 0 && options.includeProjection !== false
|
|
42
42
|
? projectRuntimeEventsToStoredMessages(runtimeEvents, { invocations: [invocation] })
|
|
43
43
|
: undefined;
|
|
44
44
|
if (projection) {
|
|
@@ -23,6 +23,7 @@ import { decodePersistedToolResultContent } from '@maka/core/tool-result-record-
|
|
|
23
23
|
import { isEmittedAgentRunEventType } from '@maka/core/agent-run';
|
|
24
24
|
import { decodeModelCallAttempt, MODEL_CALL_ATTEMPT_EVENT_TYPE, } from '@maka/core/model-call-attempt';
|
|
25
25
|
import { TOOL_RECOVERY_DECISION_FACT_KIND } from '@maka/core/tool-recovery-fact';
|
|
26
|
+
import { canonicalToolArgsHash } from '@maka/core/tool-args-identity';
|
|
26
27
|
import { buildHistoryCompactCheckpoint, historyCompactSourceDigest, matchHistoryCompactCheckpointPrefix, validateHistoryCompactCheckpointShape, } from './history-compact-checkpoint.js';
|
|
27
28
|
import { findCheckpointSummaryDefect } from './history-compact-summary-validation.js';
|
|
28
29
|
import { isHistoryCompactContentEvent } from './history-compaction.js';
|
|
@@ -281,6 +282,20 @@ function assertConversationRuntimeLedgerCopySupported(run, runtimeEvents) {
|
|
|
281
282
|
error.code = 'branch_runtime_fact_rewrite_unsupported';
|
|
282
283
|
throw error;
|
|
283
284
|
}
|
|
285
|
+
/**
|
|
286
|
+
* Mirrors the T1 scanner's identity semantics: a call whose args are not
|
|
287
|
+
* strict JSON cannot authenticate, which the scanner surfaces as a
|
|
288
|
+
* `canonical_args_hash_conflict` rather than an exception — so the copy pass
|
|
289
|
+
* must see `undefined` there, not a thrown error.
|
|
290
|
+
*/
|
|
291
|
+
const tryCanonicalToolArgsHash = (toolName, args) => {
|
|
292
|
+
try {
|
|
293
|
+
return canonicalToolArgsHash(toolName, args);
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
return undefined;
|
|
297
|
+
}
|
|
298
|
+
};
|
|
284
299
|
export async function cloneConversationRuntimeLedger(input) {
|
|
285
300
|
if (input.plan.sourceSessionId !== input.referenceMap.sourceSessionId) {
|
|
286
301
|
throw new Error('Conversation copy plan does not belong to the source Session');
|
|
@@ -386,10 +401,22 @@ export async function cloneConversationRuntimeLedger(input) {
|
|
|
386
401
|
};
|
|
387
402
|
for (const sourceId of clonedEventBySourceId.keys())
|
|
388
403
|
finishTarget(sourceId);
|
|
404
|
+
// The identity rewrite changes a copied call's canonical args, so the paired
|
|
405
|
+
// dispatch's recorded hash stops authenticating them and the copied ledger
|
|
406
|
+
// fails its own T1 re-scan. Collect the source and rewritten identities per
|
|
407
|
+
// (invocation, tool call) and re-authenticate the dispatches after the
|
|
408
|
+
// rewrite pass — but only when the source dispatch actually authenticated
|
|
409
|
+
// the source call. Stamping the rewritten identity over a mismatched hash
|
|
410
|
+
// would launder an already-corrupt source ledger into a valid-looking copy
|
|
411
|
+
// instead of preserving the corruption for the re-scan to reject (#5466
|
|
412
|
+
// review). Identity derivation mirrors the scanner's semantics: args that
|
|
413
|
+
// are not strict JSON cannot authenticate at all.
|
|
414
|
+
const rewrittenArgsIdentities = new Map();
|
|
389
415
|
for (const event of clonedEventBySourceId.values()) {
|
|
390
416
|
if (event.content?.kind === 'function_call' && event.content.name === 'Read') {
|
|
391
417
|
const args = event.content.args;
|
|
392
|
-
if (args && typeof args === 'object' && 'path' in args && typeof args.path === 'string')
|
|
418
|
+
if (args && typeof args === 'object' && 'path' in args && typeof args.path === 'string') {
|
|
419
|
+
const sourceArgsHash = tryCanonicalToolArgsHash(event.content.name, args);
|
|
393
420
|
event.content = {
|
|
394
421
|
...event.content,
|
|
395
422
|
args: {
|
|
@@ -397,19 +424,47 @@ export async function cloneConversationRuntimeLedger(input) {
|
|
|
397
424
|
...rewriteReadInput(args, references, clonedEventBySourceId),
|
|
398
425
|
},
|
|
399
426
|
};
|
|
427
|
+
rewrittenArgsIdentities.set(`${event.invocationId}\u0000${event.content.id}`, {
|
|
428
|
+
sourceArgsHash,
|
|
429
|
+
rewrittenArgsHash: tryCanonicalToolArgsHash(event.content.name, event.content.args),
|
|
430
|
+
});
|
|
431
|
+
}
|
|
400
432
|
}
|
|
401
433
|
if (event.content?.kind === 'text')
|
|
402
434
|
event.content.text = rewriteCopiedText(event.content.text, references, clonedEventBySourceId);
|
|
403
435
|
if (event.content?.kind === 'function_call' && event.content.name === 'ArchiveRead') {
|
|
404
436
|
const args = event.content.args;
|
|
405
437
|
if (args && typeof args === 'object' && 'ref' in args && typeof args.ref === 'string') {
|
|
438
|
+
const sourceArgsHash = tryCanonicalToolArgsHash(event.content.name, args);
|
|
406
439
|
event.content = {
|
|
407
440
|
...event.content,
|
|
408
441
|
args: { ...args, ref: rewriteLedgerArchiveText(args.ref, references) },
|
|
409
442
|
};
|
|
443
|
+
rewrittenArgsIdentities.set(`${event.invocationId}\u0000${event.content.id}`, {
|
|
444
|
+
sourceArgsHash,
|
|
445
|
+
rewrittenArgsHash: tryCanonicalToolArgsHash(event.content.name, event.content.args),
|
|
446
|
+
});
|
|
410
447
|
}
|
|
411
448
|
}
|
|
412
449
|
}
|
|
450
|
+
if (rewrittenArgsIdentities.size > 0) {
|
|
451
|
+
for (const event of clonedEventBySourceId.values()) {
|
|
452
|
+
const dispatch = event.actions?.toolDispatch;
|
|
453
|
+
if (dispatch?.canonicalArgsHash === undefined)
|
|
454
|
+
continue;
|
|
455
|
+
const identity = rewrittenArgsIdentities.get(`${event.invocationId}\u0000${dispatch.providerToolCallId}`);
|
|
456
|
+
if (identity === undefined)
|
|
457
|
+
continue;
|
|
458
|
+
if (identity.sourceArgsHash !== dispatch.canonicalArgsHash)
|
|
459
|
+
continue;
|
|
460
|
+
if (identity.rewrittenArgsHash === undefined)
|
|
461
|
+
continue;
|
|
462
|
+
event.actions = {
|
|
463
|
+
...event.actions,
|
|
464
|
+
toolDispatch: { ...dispatch, canonicalArgsHash: identity.rewrittenArgsHash },
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
}
|
|
413
468
|
const preparedPlans = flattenedPlans.map((plan) => {
|
|
414
469
|
const runId = runIds.get(plan.run.runId);
|
|
415
470
|
const clonedOperationalEvents = plan.operationalEvents.flatMap((event) => {
|
|
@@ -136,13 +136,20 @@ function mcpToolDescription(descriptor) {
|
|
|
136
136
|
}
|
|
137
137
|
export function mcpProxyToolName(serverId, toolName) {
|
|
138
138
|
const raw = `mcp__${sanitizeNamePart(serverId)}__${sanitizeNamePart(toolName)}`;
|
|
139
|
-
|
|
139
|
+
// Preserve existing simple names; hash identities whose spelling or separators are ambiguous.
|
|
140
|
+
if (raw.length <= MAX_PROVIDER_TOOL_NAME &&
|
|
141
|
+
sanitizeNamePart(serverId) === serverId &&
|
|
142
|
+
sanitizeNamePart(toolName) === toolName &&
|
|
143
|
+
!serverId.includes('__') &&
|
|
144
|
+
!toolName.includes('__'))
|
|
140
145
|
return raw;
|
|
141
146
|
const hash = createHash('sha256')
|
|
142
147
|
.update(`${serverId}\0${toolName}`)
|
|
143
148
|
.digest('hex')
|
|
144
149
|
.slice(0, HASH_CHARS);
|
|
145
|
-
|
|
150
|
+
// A truncated name must not become another identity's unmodified name.
|
|
151
|
+
const hashed = `mcp_h__${sanitizeNamePart(serverId)}__${sanitizeNamePart(toolName)}`;
|
|
152
|
+
return `${hashed.slice(0, MAX_PROVIDER_TOOL_NAME - HASH_CHARS - 2)}__${hash}`;
|
|
146
153
|
}
|
|
147
154
|
function sanitizeNamePart(value) {
|
|
148
155
|
const sanitized = value
|
|
@@ -17,12 +17,14 @@
|
|
|
17
17
|
* under the License.
|
|
18
18
|
*/
|
|
19
19
|
import { randomUUID } from 'node:crypto';
|
|
20
|
+
import { redactSecrets } from '@maka/core/redaction';
|
|
20
21
|
import { AsyncEventQueue } from './async-queue.js';
|
|
21
22
|
/** Converts a small plugin executor contract into Maka's existing Run event stream. */
|
|
22
23
|
export class PluginExecutorBackend {
|
|
23
24
|
kind = 'plugin-executor';
|
|
24
25
|
sessionId;
|
|
25
26
|
#cwd;
|
|
27
|
+
#configuration;
|
|
26
28
|
#instructions;
|
|
27
29
|
#model;
|
|
28
30
|
#thinkingLevel;
|
|
@@ -34,6 +36,7 @@ export class PluginExecutorBackend {
|
|
|
34
36
|
constructor(input) {
|
|
35
37
|
this.sessionId = input.sessionId;
|
|
36
38
|
this.#cwd = input.cwd;
|
|
39
|
+
this.#configuration = input.configuration;
|
|
37
40
|
this.#instructions = input.instructions;
|
|
38
41
|
this.#model = input.model;
|
|
39
42
|
this.#thinkingLevel = input.thinkingLevel;
|
|
@@ -86,6 +89,7 @@ export class PluginExecutorBackend {
|
|
|
86
89
|
const turnId = input.turnId;
|
|
87
90
|
let thinkingText = '';
|
|
88
91
|
const toolUseIds = new Map();
|
|
92
|
+
const toolOutputSequences = new Map();
|
|
89
93
|
let result;
|
|
90
94
|
let failure;
|
|
91
95
|
let failed = false;
|
|
@@ -99,6 +103,7 @@ export class PluginExecutorBackend {
|
|
|
99
103
|
cwd: this.#cwd,
|
|
100
104
|
...(this.#model ? { model: this.#model } : {}),
|
|
101
105
|
reasoningEffort: this.#thinkingLevel ?? null,
|
|
106
|
+
...(this.#configuration ? { configuration: this.#configuration } : {}),
|
|
102
107
|
...(this.#instructions ? { instructions: this.#instructions } : {}),
|
|
103
108
|
...(input.attachments ? { attachments: input.attachments } : {}),
|
|
104
109
|
...(input.directoryReferences ? { directoryReferences: input.directoryReferences } : {}),
|
|
@@ -108,8 +113,9 @@ export class PluginExecutorBackend {
|
|
|
108
113
|
onEvent: (event) => {
|
|
109
114
|
if (event.type === 'thinking_delta')
|
|
110
115
|
thinkingText += event.text;
|
|
111
|
-
this.#publishOutputEvent(turnId, messageId, event, toolUseIds, queue);
|
|
116
|
+
this.#publishOutputEvent(turnId, messageId, event, toolUseIds, toolOutputSequences, queue);
|
|
112
117
|
},
|
|
118
|
+
onPermissionRequest: (request) => this.#requestPermission(input, request, signal, toolUseIds, queue),
|
|
113
119
|
});
|
|
114
120
|
}
|
|
115
121
|
catch (error) {
|
|
@@ -130,6 +136,81 @@ export class PluginExecutorBackend {
|
|
|
130
136
|
}
|
|
131
137
|
this.#publishResult(turnId, messageId, result, queue);
|
|
132
138
|
}
|
|
139
|
+
async #requestPermission(input, request, signal, toolUseIds, queue) {
|
|
140
|
+
const hosted = input.hostedInteraction;
|
|
141
|
+
if (!hosted || signal.aborted)
|
|
142
|
+
return { outcome: 'cancelled' };
|
|
143
|
+
const requestId = this.#newId();
|
|
144
|
+
const event = {
|
|
145
|
+
type: 'form_request',
|
|
146
|
+
id: this.#newId(),
|
|
147
|
+
turnId: input.turnId,
|
|
148
|
+
ts: this.#now(),
|
|
149
|
+
requestId,
|
|
150
|
+
toolUseId: toolUseIds.get(request.toolCallId) ?? request.toolCallId,
|
|
151
|
+
message: request.title,
|
|
152
|
+
requester: {
|
|
153
|
+
name: this.#binding.identity.displayName,
|
|
154
|
+
source: this.#binding.identity.extensionId,
|
|
155
|
+
},
|
|
156
|
+
fields: [
|
|
157
|
+
{
|
|
158
|
+
kind: 'single_select',
|
|
159
|
+
name: 'optionId',
|
|
160
|
+
label: request.kind === 'question' ? 'Question' : 'Permission',
|
|
161
|
+
required: true,
|
|
162
|
+
options: request.options.map((option) => ({
|
|
163
|
+
value: option.optionId,
|
|
164
|
+
label: option.name,
|
|
165
|
+
})),
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
};
|
|
169
|
+
let settle;
|
|
170
|
+
const answer = new Promise((resolve) => {
|
|
171
|
+
settle = resolve;
|
|
172
|
+
});
|
|
173
|
+
let settled = false;
|
|
174
|
+
const finish = (result) => {
|
|
175
|
+
if (settled)
|
|
176
|
+
return;
|
|
177
|
+
settled = true;
|
|
178
|
+
settle(result);
|
|
179
|
+
};
|
|
180
|
+
const settlement = {
|
|
181
|
+
applyAnswer: async (result) => {
|
|
182
|
+
if (result.action !== 'accept')
|
|
183
|
+
return finish({ outcome: 'cancelled' });
|
|
184
|
+
const selected = result.values.optionId;
|
|
185
|
+
if (typeof selected !== 'string' ||
|
|
186
|
+
!request.options.some((option) => option.optionId === selected)) {
|
|
187
|
+
return finish({ outcome: 'cancelled' });
|
|
188
|
+
}
|
|
189
|
+
finish({ outcome: 'selected', optionId: selected });
|
|
190
|
+
},
|
|
191
|
+
applyClosure: async () => finish({ outcome: 'cancelled' }),
|
|
192
|
+
};
|
|
193
|
+
let admission;
|
|
194
|
+
const onAbort = () => {
|
|
195
|
+
finish({ outcome: 'cancelled' });
|
|
196
|
+
void Promise.resolve().then(async () => {
|
|
197
|
+
await admission?.catch(() => undefined);
|
|
198
|
+
await hosted.withdrawFormRequest(requestId).catch(() => undefined);
|
|
199
|
+
});
|
|
200
|
+
};
|
|
201
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
202
|
+
try {
|
|
203
|
+
admission = hosted.admitFormRequest({ request: event, settlement });
|
|
204
|
+
await admission;
|
|
205
|
+
if (signal.aborted)
|
|
206
|
+
return { outcome: 'cancelled' };
|
|
207
|
+
queue.push(event);
|
|
208
|
+
return await answer;
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
signal.removeEventListener('abort', onAbort);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
133
214
|
#closeOptionalOutput(turnId, messageId, thinkingText, toolUseIds, queue) {
|
|
134
215
|
if (thinkingText) {
|
|
135
216
|
queue.push({
|
|
@@ -194,9 +275,10 @@ export class PluginExecutorBackend {
|
|
|
194
275
|
turnId,
|
|
195
276
|
ts: this.#now(),
|
|
196
277
|
stopReason: 'user_stop',
|
|
278
|
+
...(result.providerStopReason ? { providerStopReason: result.providerStopReason } : {}),
|
|
197
279
|
});
|
|
198
280
|
}
|
|
199
|
-
#publishOutputEvent(turnId, messageId, event, toolUseIds, queue) {
|
|
281
|
+
#publishOutputEvent(turnId, messageId, event, toolUseIds, toolOutputSequences, queue) {
|
|
200
282
|
if (event.type === 'output_delta') {
|
|
201
283
|
if (!event.text)
|
|
202
284
|
return;
|
|
@@ -239,6 +321,7 @@ export class PluginExecutorBackend {
|
|
|
239
321
|
}
|
|
240
322
|
const toolUseId = this.#newId();
|
|
241
323
|
toolUseIds.set(event.toolCallId, toolUseId);
|
|
324
|
+
toolOutputSequences.set(event.toolCallId, 0);
|
|
242
325
|
queue.push({
|
|
243
326
|
type: 'tool_start',
|
|
244
327
|
id: this.#newId(),
|
|
@@ -257,6 +340,31 @@ export class PluginExecutorBackend {
|
|
|
257
340
|
const toolUseId = toolUseIds.get(event.toolCallId);
|
|
258
341
|
if (!toolUseId)
|
|
259
342
|
return;
|
|
343
|
+
if (event.type === 'tool_output_delta') {
|
|
344
|
+
if (!event.text)
|
|
345
|
+
return;
|
|
346
|
+
const chunk = redactSecrets(event.text);
|
|
347
|
+
if (!chunk)
|
|
348
|
+
return;
|
|
349
|
+
const now = this.#now();
|
|
350
|
+
const seq = (toolOutputSequences.get(event.toolCallId) ?? 0) + 1;
|
|
351
|
+
toolOutputSequences.set(event.toolCallId, seq);
|
|
352
|
+
queue.push({
|
|
353
|
+
type: 'tool_output_delta',
|
|
354
|
+
id: this.#newId(),
|
|
355
|
+
sessionId: this.sessionId,
|
|
356
|
+
turnId,
|
|
357
|
+
ts: now,
|
|
358
|
+
toolCallId: toolUseId,
|
|
359
|
+
toolUseId,
|
|
360
|
+
seq,
|
|
361
|
+
stream: event.stream ?? 'stdout',
|
|
362
|
+
chunk,
|
|
363
|
+
redacted: chunk !== event.text,
|
|
364
|
+
createdAt: now,
|
|
365
|
+
});
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
260
368
|
if (event.type === 'tool_progress') {
|
|
261
369
|
if (!event.text)
|
|
262
370
|
return;
|
|
@@ -271,6 +379,7 @@ export class PluginExecutorBackend {
|
|
|
271
379
|
return;
|
|
272
380
|
}
|
|
273
381
|
toolUseIds.delete(event.toolCallId);
|
|
382
|
+
toolOutputSequences.delete(event.toolCallId);
|
|
274
383
|
queue.push({
|
|
275
384
|
type: 'tool_result',
|
|
276
385
|
id: this.#newId(),
|
|
@@ -279,7 +388,9 @@ export class PluginExecutorBackend {
|
|
|
279
388
|
toolUseId,
|
|
280
389
|
providerExecuted: true,
|
|
281
390
|
isError: event.isError ?? false,
|
|
282
|
-
content:
|
|
391
|
+
content: event.content.kind === 'text'
|
|
392
|
+
? event.content
|
|
393
|
+
: { kind: 'file_diff', paths: [...event.content.paths], diff: event.content.diff },
|
|
283
394
|
});
|
|
284
395
|
}
|
|
285
396
|
#publishFailure(turnId, message, code, recoverable, queue) {
|
|
@@ -311,6 +422,8 @@ function cancellationEventReason(result) {
|
|
|
311
422
|
return 'redirect';
|
|
312
423
|
if (result.reason === 'timeout')
|
|
313
424
|
return 'timeout';
|
|
425
|
+
if (result.reason === 'crash')
|
|
426
|
+
return 'crash';
|
|
314
427
|
if (result.source === 'executor_retired')
|
|
315
428
|
return 'crash';
|
|
316
429
|
return 'user_stop';
|