maka-agent 0.2.0-dev.46.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/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-capability-provider-command.js +25 -6
- package/dist/runtime-host-service-manager.js +26 -2
- package/dist/tui-context-refresh.js +15 -26
- package/dist/tui-mcp-control.js +66 -112
- 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/client-capability-grant.js +2 -2
- 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/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-event-store.js +25 -0
- package/node_modules/@maka/core/dist/runtime-invocation.js +5 -0
- package/node_modules/@maka/core/dist/runtime-policy/connection-catalog-codec.js +4 -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 +19 -1
- package/node_modules/@maka/core/dist/settings.js +3 -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 +3 -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/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/conversation-copy.js +56 -1
- package/node_modules/@maka/runtime/dist/mcp-tools.js +9 -2
- 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 +122 -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 +184 -126
- 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/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/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/reconnecting-connection.js +4 -4
- 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/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 +20 -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 +75 -5
- package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +1 -5
- 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-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/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 +188 -21
- package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +6 -0
- package/node_modules/@maka/runtime-host/dist/server/extension-bundle.js +4 -0
- package/node_modules/@maka/runtime-host/dist/server/external-session-coordinator.js +29 -7
- 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 +5 -1
- 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-bundle-coordinator.js +4 -1
- package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +128 -13
- package/node_modules/@maka/runtime-host/dist/server/session-effect-coordinator.js +12 -6
- package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +2 -2
- 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",
|
|
@@ -133,6 +135,7 @@
|
|
|
133
135
|
"./computer-use": "./dist/computer-use.js",
|
|
134
136
|
"./connection-error-copy": "./dist/connection-error-copy.js",
|
|
135
137
|
"./connection-usage": "./dist/connection-usage.js",
|
|
138
|
+
"./refresh-read-coordinator": "./dist/refresh-read-coordinator.js",
|
|
136
139
|
"./git-review": "./dist/git-review.js",
|
|
137
140
|
"./graph-command": "./dist/graph-command.js",
|
|
138
141
|
"./project": "./dist/project.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) {
|
|
@@ -59,7 +59,7 @@ function isImageToolResult(value) {
|
|
|
59
59
|
typeof image.ref === 'object');
|
|
60
60
|
}
|
|
61
61
|
function toolResultText(text) {
|
|
62
|
-
return { type: '
|
|
62
|
+
return { type: 'text', text };
|
|
63
63
|
}
|
|
64
64
|
function nativeApplyPatchFailureOutput(output) {
|
|
65
65
|
const value = output.type === 'json' || output.type === 'error-json' ? output.value : undefined;
|
|
@@ -538,18 +538,24 @@ export class AiSdkMessageProjection {
|
|
|
538
538
|
async materializeToolResultOutput(budget, output, isError, decisionKey) {
|
|
539
539
|
if (isError || !isImageToolResult(output))
|
|
540
540
|
return toolResultOutput(output, isError);
|
|
541
|
+
return {
|
|
542
|
+
type: 'content',
|
|
543
|
+
value: [await this.materializeImage(budget, output.ref, output.mimeType, decisionKey)],
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
async materializeImage(budget, ref, mediaType, decisionKey) {
|
|
541
547
|
if (this.input.supportsVision !== true) {
|
|
542
548
|
return toolResultText('Image was read, but the selected model does not support image input.');
|
|
543
549
|
}
|
|
544
550
|
if (!this.input.readAttachmentBytes) {
|
|
545
551
|
return toolResultText('Image was read, but its stored bytes are unavailable.');
|
|
546
552
|
}
|
|
547
|
-
if (budget
|
|
553
|
+
if (budget.decisions.get(decisionKey) === false) {
|
|
548
554
|
return toolResultText(PROVIDER_IMAGE_BUDGET_EXCEEDED_MESSAGE);
|
|
549
555
|
}
|
|
550
556
|
let read;
|
|
551
557
|
try {
|
|
552
|
-
read = await this.input.readAttachmentBytes(
|
|
558
|
+
read = await this.input.readAttachmentBytes(ref);
|
|
553
559
|
}
|
|
554
560
|
catch {
|
|
555
561
|
return toolResultText('Image could not be loaded from artifact storage: read_failed.');
|
|
@@ -561,18 +567,9 @@ export class AiSdkMessageProjection {
|
|
|
561
567
|
return toolResultText(PROVIDER_IMAGE_BUDGET_EXCEEDED_MESSAGE);
|
|
562
568
|
}
|
|
563
569
|
return {
|
|
564
|
-
type: '
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
{
|
|
568
|
-
type: 'file',
|
|
569
|
-
data: {
|
|
570
|
-
type: 'data',
|
|
571
|
-
data: Buffer.from(read.bytes).toString('base64'),
|
|
572
|
-
},
|
|
573
|
-
mediaType: output.mimeType,
|
|
574
|
-
},
|
|
575
|
-
],
|
|
570
|
+
type: 'file',
|
|
571
|
+
data: { type: 'data', data: Buffer.from(read.bytes).toString('base64') },
|
|
572
|
+
mediaType,
|
|
576
573
|
};
|
|
577
574
|
}
|
|
578
575
|
async materializeDurableToolResultProjection(budget, projection, decisionKey) {
|
|
@@ -580,51 +577,9 @@ export class AiSdkMessageProjection {
|
|
|
580
577
|
return durableProjectionToToolResultOutput(projection);
|
|
581
578
|
const value = [];
|
|
582
579
|
for (const [index, part] of projection.parts.entries()) {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
}
|
|
587
|
-
if (this.input.supportsVision !== true) {
|
|
588
|
-
value.push({
|
|
589
|
-
type: 'text',
|
|
590
|
-
text: 'Image was read, but the selected model does not support image input.',
|
|
591
|
-
});
|
|
592
|
-
continue;
|
|
593
|
-
}
|
|
594
|
-
if (!this.input.readAttachmentBytes) {
|
|
595
|
-
value.push({
|
|
596
|
-
type: 'text',
|
|
597
|
-
text: 'Image was read, but its stored bytes are unavailable.',
|
|
598
|
-
});
|
|
599
|
-
continue;
|
|
600
|
-
}
|
|
601
|
-
let read;
|
|
602
|
-
try {
|
|
603
|
-
read = await this.input.readAttachmentBytes(part.ref);
|
|
604
|
-
}
|
|
605
|
-
catch {
|
|
606
|
-
value.push({
|
|
607
|
-
type: 'text',
|
|
608
|
-
text: 'Image could not be loaded from artifact storage: read_failed.',
|
|
609
|
-
});
|
|
610
|
-
continue;
|
|
611
|
-
}
|
|
612
|
-
if (!read.ok) {
|
|
613
|
-
value.push({
|
|
614
|
-
type: 'text',
|
|
615
|
-
text: `Image could not be loaded from artifact storage: ${read.reason}.`,
|
|
616
|
-
});
|
|
617
|
-
continue;
|
|
618
|
-
}
|
|
619
|
-
if (!this.chargeImageBudget(budget, read.bytes.length, `${decisionKey}:artifact:${index}`)) {
|
|
620
|
-
value.push({ type: 'text', text: PROVIDER_IMAGE_BUDGET_EXCEEDED_MESSAGE });
|
|
621
|
-
continue;
|
|
622
|
-
}
|
|
623
|
-
value.push({
|
|
624
|
-
type: 'file',
|
|
625
|
-
data: { type: 'data', data: Buffer.from(read.bytes).toString('base64') },
|
|
626
|
-
mediaType: part.mediaType,
|
|
627
|
-
});
|
|
580
|
+
value.push(part.kind === 'text'
|
|
581
|
+
? toolResultText(part.text)
|
|
582
|
+
: await this.materializeImage(budget, part.ref, part.mediaType, `${decisionKey}:artifact:${index}`));
|
|
628
583
|
}
|
|
629
584
|
return { type: 'content', value };
|
|
630
585
|
}
|
|
@@ -35,6 +35,7 @@ import { decodePlaintextResponsesReasoningState, responsesReasoningItemId, } fro
|
|
|
35
35
|
import { finitePositive } from './context-budget-helpers.js';
|
|
36
36
|
import { contextDiagnosticsCompactionOf, } from './context-diagnostics.js';
|
|
37
37
|
import { hasBlockingReplayDiagnostics } from './ai-sdk-compaction.js';
|
|
38
|
+
import { portableApplyPatchTool } from './apply-patch-profile.js';
|
|
38
39
|
import { RunTrace } from './run-trace.js';
|
|
39
40
|
import { REQUEST_SANDBOX_BOUNDARY_TOOL_NAME, SANDBOX_BOUNDARY_DENIED_FOR_TURN, SANDBOX_BOUNDARY_FINALIZATION_PROMPT, } from './sandbox-boundary-tool.js';
|
|
40
41
|
import { buildRuntimeEventModelReplayPlan, buildSteeringEnvelope, collectToolActivityTurnIds, compatibleProviderReasoningReplayEventIds, formatTextWithInlineRefs, steeringMessagesMissingFromBase, steeringModelMessage, } from './model-history.js';
|
|
@@ -251,6 +252,10 @@ function renderCodeModeCatalogPrompt(nested) {
|
|
|
251
252
|
function nestableToolSnapshot(providerTools, activeToolNames) {
|
|
252
253
|
const active = new Set(activeToolNames);
|
|
253
254
|
return new Map(providerTools
|
|
255
|
+
.map((tool) => tool.providerTool?.kind === 'openai-apply-patch' ||
|
|
256
|
+
tool.providerTool?.kind === 'codex-apply-patch'
|
|
257
|
+
? portableApplyPatchTool(tool)
|
|
258
|
+
: tool)
|
|
254
259
|
.filter((tool) => active.has(tool.name) &&
|
|
255
260
|
tool.name !== INVALID_TOOL_NAME &&
|
|
256
261
|
tool.name !== 'exec' &&
|
|
@@ -348,6 +353,7 @@ function joinPromptFragments(fragments) {
|
|
|
348
353
|
}
|
|
349
354
|
const MAX_WAITING_CODE_MODE_CELLS = 1;
|
|
350
355
|
const MAX_PROVIDER_ATTEMPTS_PER_STEP = 10;
|
|
356
|
+
const CONTEXT_RECOVERY_MAX_OUTPUT_TOKENS = 8_000;
|
|
351
357
|
const PROVIDER_RETRY_BASE_DELAY_MS = 1_000;
|
|
352
358
|
const PROVIDER_RETRY_MAX_DELAY_MS = 32_000;
|
|
353
359
|
const PROVIDER_RETRY_JITTER_FACTOR = 0.25;
|
|
@@ -1168,6 +1174,7 @@ export class AiSdkTurn {
|
|
|
1168
1174
|
: undefined;
|
|
1169
1175
|
providerRequestTracker?.setStep(runtimeSteps, requestCompositionId);
|
|
1170
1176
|
let attemptMessages = projectedMessages;
|
|
1177
|
+
let overflowRecoveryMaxOutputTokens;
|
|
1171
1178
|
let providerAttempt = 0;
|
|
1172
1179
|
const returnedToolCalls = [];
|
|
1173
1180
|
const providerToolInputs = new Map();
|
|
@@ -1205,6 +1212,8 @@ export class AiSdkTurn {
|
|
|
1205
1212
|
? nestedTools
|
|
1206
1213
|
: undefined;
|
|
1207
1214
|
const requestWatchdog = watchdogState.current;
|
|
1215
|
+
const requestMaxOutputTokens = overflowRecoveryMaxOutputTokens ??
|
|
1216
|
+
this.deps.modelAdapter.maxOutputTokensForInput(midTurnState?.baselineTokens ?? midTurnState?.lastAcceptedTotalTokens);
|
|
1208
1217
|
// Read here, beside the messages it describes: `attemptMessages` is
|
|
1209
1218
|
// rebuilt in place by overflow recovery, and the boundary it folded
|
|
1210
1219
|
// under must travel with that rebuild, not with the step.
|
|
@@ -1232,6 +1241,9 @@ export class AiSdkTurn {
|
|
|
1232
1241
|
...(providerRequestTracker ? { providerRequestTracker } : {}),
|
|
1233
1242
|
...(historyCompactBoundary ? { historyCompactBoundary } : {}),
|
|
1234
1243
|
continuationKey: this.turnId,
|
|
1244
|
+
...(requestMaxOutputTokens !== undefined
|
|
1245
|
+
? { maxOutputTokens: requestMaxOutputTokens }
|
|
1246
|
+
: {}),
|
|
1235
1247
|
});
|
|
1236
1248
|
for await (const event of result.events) {
|
|
1237
1249
|
if (this.aborted)
|
|
@@ -1418,6 +1430,11 @@ export class AiSdkTurn {
|
|
|
1418
1430
|
consumeWatchdogTimeout();
|
|
1419
1431
|
providerOutcome = await result.outcome;
|
|
1420
1432
|
if (providerOutcome.kind === 'completed') {
|
|
1433
|
+
// A compacted overflow retry only needs the conservative cap for
|
|
1434
|
+
// that one request. Once the provider accepts the request, the
|
|
1435
|
+
// next step can use the fresh provider count to derive a larger
|
|
1436
|
+
// safe cap again.
|
|
1437
|
+
overflowRecoveryMaxOutputTokens = undefined;
|
|
1421
1438
|
runtimeSteps += 1;
|
|
1422
1439
|
const stepUsage = providerOutcome.usage;
|
|
1423
1440
|
providerStepUsage = stepUsage;
|
|
@@ -1616,6 +1633,7 @@ export class AiSdkTurn {
|
|
|
1616
1633
|
: undefined;
|
|
1617
1634
|
if (recovered) {
|
|
1618
1635
|
attemptMessages = recovered.messages;
|
|
1636
|
+
overflowRecoveryMaxOutputTokens = Math.min(this.deps.modelAdapter.maxOutputTokens() ?? CONTEXT_RECOVERY_MAX_OUTPUT_TOKENS, CONTEXT_RECOVERY_MAX_OUTPUT_TOKENS);
|
|
1619
1637
|
continue;
|
|
1620
1638
|
}
|
|
1621
1639
|
// Window suggestion (#4559): the provider rejected a request and
|
|
@@ -16,17 +16,30 @@
|
|
|
16
16
|
* specific language governing permissions and limitations
|
|
17
17
|
* under the License.
|
|
18
18
|
*/
|
|
19
|
-
import {
|
|
19
|
+
import { modelApplyPatchEnabled } from '@maka/core/model-thinking';
|
|
20
|
+
import { z } from 'zod';
|
|
21
|
+
import { CODEX_PATCH_DESCRIPTION, parseCodexV4aPatch } from './codex-v4a-patch.js';
|
|
20
22
|
import { openAiModelSupportsApplyPatch } from './openai-apply-patch.js';
|
|
21
|
-
|
|
23
|
+
const portableApplyPatchParameters = z.object({ patch: z.string() });
|
|
24
|
+
/** Project a provider-native ApplyPatch tool into the portable client-executed shape. */
|
|
25
|
+
export function portableApplyPatchTool(tool) {
|
|
26
|
+
return {
|
|
27
|
+
...tool,
|
|
28
|
+
description: CODEX_PATCH_DESCRIPTION,
|
|
29
|
+
parameters: portableApplyPatchParameters,
|
|
30
|
+
providerTool: undefined,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** User overrides take precedence; new models can opt in through ordinary function calling. */
|
|
22
34
|
export function resolveApplyPatchProfile(runtime, modelId) {
|
|
23
|
-
if (
|
|
35
|
+
if (!modelApplyPatchEnabled(modelId, { applyPatch: runtime.enabled }))
|
|
24
36
|
return null;
|
|
25
|
-
const
|
|
26
|
-
|
|
37
|
+
const structured = runtime.wire === 'openai-responses' &&
|
|
38
|
+
runtime.applyPatchProtocol === 'openai-structured' &&
|
|
39
|
+
openAiModelSupportsApplyPatch(modelId.trim().toLowerCase());
|
|
40
|
+
if (structured)
|
|
27
41
|
return { kind: 'openai-structured' };
|
|
28
|
-
}
|
|
29
|
-
return null;
|
|
42
|
+
return runtime.customTools ? { kind: 'codex-v4a-freeform' } : { kind: 'portable-v4a' };
|
|
30
43
|
}
|
|
31
44
|
/** Project one verified profile into an exclusive model-facing editing surface. */
|
|
32
45
|
export function routeApplyPatchTools(tools, profile) {
|
|
@@ -35,19 +48,40 @@ export function routeApplyPatchTools(tools, profile) {
|
|
|
35
48
|
return [...tools];
|
|
36
49
|
if (!profile)
|
|
37
50
|
return tools.filter((tool) => tool !== applyPatchTool);
|
|
38
|
-
|
|
51
|
+
const routed = tools.filter((tool) => tool.name !== 'Write' && tool.name !== 'Edit');
|
|
52
|
+
if (profile.kind === 'openai-structured')
|
|
53
|
+
return routed;
|
|
54
|
+
return routed.map((tool) => tool !== applyPatchTool
|
|
55
|
+
? tool
|
|
56
|
+
: profile.kind === 'codex-v4a-freeform'
|
|
57
|
+
? {
|
|
58
|
+
...tool,
|
|
59
|
+
description: CODEX_PATCH_DESCRIPTION,
|
|
60
|
+
parameters: z.string(),
|
|
61
|
+
providerTool: { kind: 'codex-apply-patch' },
|
|
62
|
+
}
|
|
63
|
+
: portableApplyPatchTool(tool));
|
|
39
64
|
}
|
|
40
|
-
/**
|
|
65
|
+
/** Re-encode history for the current tool transport, or preserve it as facts when disabled. */
|
|
41
66
|
export function normalizeApplyPatchReplayInput(profile, toolCallId, input) {
|
|
42
67
|
// A missing profile means the target request does not advertise ApplyPatch.
|
|
43
68
|
// Returning the historical input would serialize a call to an undeclared
|
|
44
69
|
// tool; route it through the durable-fact downgrade instead.
|
|
45
70
|
if (!profile)
|
|
46
71
|
return null;
|
|
47
|
-
|
|
48
|
-
|
|
72
|
+
const patch = patchText(input);
|
|
73
|
+
if (profile.kind !== 'openai-structured') {
|
|
74
|
+
const text = patch ?? structuredPatchText(input);
|
|
75
|
+
if (text === null)
|
|
76
|
+
return null;
|
|
77
|
+
if (profile.kind === 'codex-v4a-freeform')
|
|
78
|
+
return text;
|
|
79
|
+
return patch !== null && typeof input === 'object' ? input : { patch: text };
|
|
80
|
+
}
|
|
81
|
+
if (patch === null)
|
|
82
|
+
return structuredApplyPatchOperation(input) ? input : null;
|
|
49
83
|
try {
|
|
50
|
-
const operations = parseCodexV4aPatch(
|
|
84
|
+
const operations = parseCodexV4aPatch(patch);
|
|
51
85
|
return operations.length === 1 ? { callId: toolCallId, operation: operations[0] } : null;
|
|
52
86
|
}
|
|
53
87
|
catch {
|
|
@@ -57,9 +91,10 @@ export function normalizeApplyPatchReplayInput(profile, toolCallId, input) {
|
|
|
57
91
|
/** Preserve an executed multi-file patch when the target wire cannot replay one call for it. */
|
|
58
92
|
export function applyPatchReplayFactText(input, output, isError) {
|
|
59
93
|
let operations;
|
|
60
|
-
|
|
94
|
+
const patch = patchText(input);
|
|
95
|
+
if (patch !== null) {
|
|
61
96
|
try {
|
|
62
|
-
operations = parseCodexV4aPatch(
|
|
97
|
+
operations = parseCodexV4aPatch(patch);
|
|
63
98
|
}
|
|
64
99
|
catch {
|
|
65
100
|
return null;
|
|
@@ -129,3 +164,18 @@ function structuredApplyPatchOperation(input) {
|
|
|
129
164
|
}
|
|
130
165
|
return null;
|
|
131
166
|
}
|
|
167
|
+
function patchText(input) {
|
|
168
|
+
if (typeof input === 'string')
|
|
169
|
+
return input;
|
|
170
|
+
if (input && typeof input === 'object' && 'patch' in input && typeof input.patch === 'string')
|
|
171
|
+
return input.patch;
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
function structuredPatchText(input) {
|
|
175
|
+
const op = structuredApplyPatchOperation(input);
|
|
176
|
+
if (!op)
|
|
177
|
+
return null;
|
|
178
|
+
const action = op.type === 'create_file' ? 'Add' : op.type === 'delete_file' ? 'Delete' : 'Update';
|
|
179
|
+
const body = op.type === 'delete_file' ? '' : op.diff.endsWith('\n') ? op.diff : `${op.diff}\n`;
|
|
180
|
+
return `*** Begin Patch\n*** ${action} File: ${op.path}\n${body}*** End Patch`;
|
|
181
|
+
}
|
|
@@ -161,10 +161,11 @@ export function buildBuiltinTools(options = {}) {
|
|
|
161
161
|
providerTool: { kind: 'openai-apply-patch' },
|
|
162
162
|
executionFacts,
|
|
163
163
|
impl: async (input, ctx) => {
|
|
164
|
-
|
|
164
|
+
const patch = typeof input === 'string' ? input : input.patch;
|
|
165
|
+
if (typeof patch !== 'string') {
|
|
165
166
|
return await filesystem.applyPatch({ operation: input.operation, ...filesystemCall(ctx) });
|
|
166
167
|
}
|
|
167
|
-
const operations = parseCodexV4aPatch(
|
|
168
|
+
const operations = parseCodexV4aPatch(patch);
|
|
168
169
|
return await executeApplyPatchOperations(operations, async (operation) => {
|
|
169
170
|
await filesystem.applyPatch({ operation, ...filesystemCall(ctx) });
|
|
170
171
|
}, ctx.abortSignal);
|
|
@@ -16,6 +16,24 @@
|
|
|
16
16
|
* specific language governing permissions and limitations
|
|
17
17
|
* under the License.
|
|
18
18
|
*/
|
|
19
|
+
export const CODEX_PATCH_DESCRIPTION = `Edit files using a patch enclosed in *** Begin Patch and *** End Patch.
|
|
20
|
+
Use *** Add File: path with each content line prefixed by +; *** Delete File: path;
|
|
21
|
+
or *** Update File: path with @@ context markers and lines prefixed by space (unchanged),
|
|
22
|
+
- (removed), or + (added). Include enough unchanged context to locate each edit.
|
|
23
|
+
One patch may contain multiple file operations. Moves are not supported: add the destination
|
|
24
|
+
and delete the source instead. Read existing files before patching them.`;
|
|
25
|
+
// Match the executor's supported subset: intentionally no Move to production.
|
|
26
|
+
export const CODEX_PATCH_GRAMMAR = String.raw `start: begin_patch hunk+ end_patch
|
|
27
|
+
begin_patch: "*** Begin Patch" LF
|
|
28
|
+
end_patch: "*** End Patch" LF?
|
|
29
|
+
hunk: add_hunk | delete_hunk | update_hunk
|
|
30
|
+
add_hunk: "*** Add File: " filename LF add_line+
|
|
31
|
+
delete_hunk: "*** Delete File: " filename LF
|
|
32
|
+
update_hunk: "*** Update File: " filename LF change+
|
|
33
|
+
filename: /[^\r\n]+/
|
|
34
|
+
add_line: "+" /[^\r\n]+/? LF
|
|
35
|
+
change: ("@@" | "@@ " /[^\r\n]+/) LF | ("+" | "-" | " ") /[^\r\n]+/? LF | "*** End of File" LF
|
|
36
|
+
%import common.LF`;
|
|
19
37
|
export class CodexV4aPatchError extends Error {
|
|
20
38
|
constructor(message) {
|
|
21
39
|
super(message);
|