maka-agent 0.2.0-dev.26.20260909 → 0.2.0-dev.28.20260911
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/LICENSE +106 -0
- package/dist/cli-core.js +28 -0
- package/dist/runtime-host-cli.js +43 -3
- package/dist/runtime-host-handoff-surface.js +10 -8
- package/dist/runtime-host-lifecycle-transaction.js +28 -3
- package/dist/runtime-host-local-source-retirement.js +1 -1
- package/dist/runtime-host-run-command.js +15 -7
- package/dist/runtime-host-setup-command.js +29 -6
- package/dist/runtime-host-update-command.js +49 -5
- package/dist/session-export-command.js +72 -0
- package/dist/session-import-command.js +69 -0
- package/native/runtime-host-peer/prebuilds/darwin-arm64/maka_runtime_host_peer.node +0 -0
- package/native/runtime-host-peer/prebuilds/linux-arm64/maka_runtime_host_peer.node +0 -0
- package/native/runtime-host-peer/prebuilds/linux-x64/maka_runtime_host_peer.node +0 -0
- package/native/runtime-host-peer/prebuilds/win32-x64/maka_runtime_host_peer.node +0 -0
- package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +9 -4
- package/node_modules/@maka/core/dist/diagnostic-log.js +18 -7
- package/node_modules/@maka/core/dist/text-sanitize.js +8 -2
- package/node_modules/@maka/core/dist/usage-stats/types.js +2 -0
- package/node_modules/@maka/core/dist/workhub-routing.js +126 -0
- package/node_modules/@maka/core/package.json +2 -1
- package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +15 -11
- package/node_modules/@maka/runtime/dist/archive-read-tool.js +3 -1
- package/node_modules/@maka/runtime/dist/bots/bot-test.js +2 -1
- package/node_modules/@maka/runtime/dist/bots/feishu-bridge.js +24 -20
- package/node_modules/@maka/runtime/dist/bots/slack-bridge.js +5 -3
- package/node_modules/@maka/runtime/dist/bots/wechat-bridge.js +87 -29
- package/node_modules/@maka/runtime/dist/bots/wecom-bridge.js +40 -36
- package/node_modules/@maka/runtime/dist/builtin-tools.js +21 -2
- package/node_modules/@maka/runtime/dist/computer-use-tools.js +4 -1
- package/node_modules/@maka/runtime/dist/deep-research-tools.js +11 -5
- package/node_modules/@maka/runtime/dist/edit-replace.js +2 -2
- package/node_modules/@maka/runtime/dist/filesystem-worker/operations.js +5 -6
- package/node_modules/@maka/runtime/dist/memory-extraction-evidence.js +7 -1
- package/node_modules/@maka/runtime/dist/memory-extraction.js +12 -7
- package/node_modules/@maka/runtime/dist/openai-responses-websocket.js +2 -5
- package/node_modules/@maka/runtime/dist/plugin-agent-service.js +131 -0
- package/node_modules/@maka/runtime/dist/plugin-approval-service.js +33 -0
- package/node_modules/@maka/runtime/dist/plugin-attachment-service.js +53 -0
- package/node_modules/@maka/runtime/dist/plugin-command-service.js +100 -0
- package/node_modules/@maka/runtime/dist/plugin-composition-loader.js +11 -2
- package/node_modules/@maka/runtime/dist/plugin-data-services.js +332 -0
- package/node_modules/@maka/runtime/dist/plugin-fs-service.js +62 -0
- package/node_modules/@maka/runtime/dist/plugin-goal-service.js +61 -0
- package/node_modules/@maka/runtime/dist/plugin-invocation-signal.js +24 -0
- package/node_modules/@maka/runtime/dist/plugin-llm-service.js +85 -0
- package/node_modules/@maka/runtime/dist/plugin-lsp-service.js +94 -0
- package/node_modules/@maka/runtime/dist/plugin-scope-registry.js +91 -0
- package/node_modules/@maka/runtime/dist/plugin-session-query-service.js +71 -0
- package/node_modules/@maka/runtime/dist/plugin-shell-env-service.js +87 -0
- package/node_modules/@maka/runtime/dist/plugin-shell-service.js +74 -0
- package/node_modules/@maka/runtime/dist/plugin-skill-service.js +93 -0
- package/node_modules/@maka/runtime/dist/plugin-system-prompt-service.js +346 -0
- package/node_modules/@maka/runtime/dist/plugin-tool-service.js +18 -52
- package/node_modules/@maka/runtime/dist/plugin-user-question-service.js +43 -0
- package/node_modules/@maka/runtime/dist/plugin-web-service.js +69 -0
- package/node_modules/@maka/runtime/dist/request-customization-fetch.js +14 -7
- package/node_modules/@maka/runtime/dist/session-export.js +178 -0
- package/node_modules/@maka/runtime/dist/session-import.js +100 -0
- package/node_modules/@maka/runtime/dist/session-manager.js +6 -1
- package/node_modules/@maka/runtime/dist/shell-run-manager.js +9 -6
- package/node_modules/@maka/runtime/dist/shell-run-tool-result.js +13 -6
- package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +43 -10
- package/node_modules/@maka/runtime/dist/stream-graph-handoff.js +6 -1
- package/node_modules/@maka/runtime/dist/subscription-model-fetch.js +1 -4
- package/node_modules/@maka/runtime/dist/system-prompt/workspace-instructions.js +11 -6
- package/node_modules/@maka/runtime/dist/tavily-search.js +8 -2
- package/node_modules/@maka/runtime/dist/text-line-window.js +49 -0
- package/node_modules/@maka/runtime/dist/tool-output.js +35 -12
- package/node_modules/@maka/runtime/dist/unified-diff.js +40 -10
- package/node_modules/@maka/runtime/dist/web-fetch-tool.js +4 -2
- package/node_modules/@maka/runtime/dist/workers/filesystem-worker.js +82 -33
- package/node_modules/@maka/runtime/dist/workspace-executor.js +2 -6
- package/node_modules/@maka/runtime/package.json +20 -1
- package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +73 -24
- package/node_modules/@maka/runtime-host/dist/client/host-handoff.js +46 -12
- package/node_modules/@maka/runtime-host/dist/operator/setup-frame.js +31 -21
- package/node_modules/@maka/runtime-host/dist/peer-mesh/node.js +7 -4
- package/node_modules/@maka/runtime-host/dist/protocol/artifact.js +2 -2
- package/node_modules/@maka/runtime-host/dist/protocol/index.js +10 -1
- package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +30 -5
- package/node_modules/@maka/runtime-host/dist/protocol/session-transcript.js +3 -2
- package/node_modules/@maka/runtime-host/dist/protocol/skill-catalog.js +2 -1
- package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +25 -23
- package/node_modules/@maka/runtime-host/dist/server/artifact-coordinator.js +2 -0
- package/node_modules/@maka/runtime-host/dist/server/client-capability-coordinator.js +62 -4
- package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +481 -11
- package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +74 -0
- package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +6 -1
- package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +2 -2
- package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +30 -0
- package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +114 -20
- package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +49 -12
- package/node_modules/@maka/runtime-host/dist/server/plugin-data-runtime.js +234 -0
- package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +6 -0
- package/node_modules/@maka/runtime-host/dist/server/plugin-platform.js +12 -0
- package/node_modules/@maka/runtime-host/dist/server/root-admission-owner.js +17 -8
- package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +189 -92
- package/node_modules/@maka/runtime-host/dist/server/runtime-resource-projection.js +3 -9
- package/node_modules/@maka/runtime-host/dist/server/session-continuity-coordinator.js +20 -21
- package/node_modules/@maka/runtime-host/dist/server/session-transcript-pager.js +24 -2
- package/node_modules/@maka/runtime-host/dist/server/skill-catalog-coordinator.js +18 -2
- package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +44 -35
- package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +92 -10
- package/node_modules/@maka/runtime-host/dist/transport/framed-byte-stream-transport.js +4 -1
- package/node_modules/@maka/runtime-host/dist/transport/local-ipc-framing.js +4 -2
- package/node_modules/@maka/runtime-host/dist/transport/peer-native.js +9 -8
- package/node_modules/@maka/runtime-host/dist/transport/resumable-peer-stream.js +14 -9
- package/node_modules/@maka/storage/dist/agent-run-store.js +54 -10
- package/node_modules/@maka/storage/dist/context-offload-snapshot.js +32 -10
- package/node_modules/@maka/storage/dist/context-value-mutation-gate.js +59 -0
- package/node_modules/@maka/storage/dist/execution-stores.js +1 -1
- package/node_modules/@maka/storage/dist/session-bundle-policy.js +909 -34
- package/node_modules/@maka/storage/dist/session-message-projection.js +8 -4
- package/node_modules/@maka/storage/dist/session-store.js +2 -2
- package/node_modules/@maka/storage/dist/sqlite-context-offload-store.js +18 -4
- package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +12 -5
- package/node_modules/@maka/storage/dist/stable-storage.js +2 -1
- package/node_modules/@maka/storage/package.json +2 -0
- package/node_modules/@modelcontextprotocol/client/dist/src-D_zzAWoS.mjs +22 -5
- package/node_modules/@modelcontextprotocol/client/dist/src-NAgB4Mp8.cjs +22 -5
- package/node_modules/zod/v4/core/memoizer.cjs +12 -16
- package/node_modules/zod/v4/core/memoizer.js +12 -16
- package/package.json +1 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { Service } from './plugin-kernel.js';
|
|
20
|
+
import { pluginInvocationSignal } from './plugin-invocation-signal.js';
|
|
21
|
+
import { MakaPluginRuntimeError, pluginIdentity, registerPluginContribution, } from './plugin-runtime.js';
|
|
22
|
+
import { PluginScopeRegistry } from './plugin-scope-registry.js';
|
|
23
|
+
/** Metered Host model calls plus an ordered plugin adapter seam. */
|
|
24
|
+
export class PluginLlmService extends Service {
|
|
25
|
+
agents;
|
|
26
|
+
llmRuntime;
|
|
27
|
+
adapters = new PluginScopeRegistry();
|
|
28
|
+
constructor(ctx, agents) {
|
|
29
|
+
super(ctx, 'llm');
|
|
30
|
+
this.agents = agents;
|
|
31
|
+
}
|
|
32
|
+
bindRuntime(runtime) {
|
|
33
|
+
if (this.ctx.maka)
|
|
34
|
+
throw new Error('Only the Host may bind the LLM Runtime');
|
|
35
|
+
if (this.llmRuntime)
|
|
36
|
+
throw new Error('Plugin LLM Runtime is already bound');
|
|
37
|
+
this.llmRuntime = runtime;
|
|
38
|
+
return this.ctx.effect(() => () => {
|
|
39
|
+
if (this.llmRuntime === runtime)
|
|
40
|
+
this.llmRuntime = undefined;
|
|
41
|
+
}, 'llm.bindRuntime()');
|
|
42
|
+
}
|
|
43
|
+
register(adapter) {
|
|
44
|
+
if (!adapter || !/^[A-Za-z][A-Za-z0-9._:-]{0,127}$/u.test(adapter.id)) {
|
|
45
|
+
throw new TypeError('LLM adapter id is invalid');
|
|
46
|
+
}
|
|
47
|
+
if (typeof adapter.supports !== 'function' || typeof adapter.generate !== 'function') {
|
|
48
|
+
throw new TypeError(`LLM adapter implementation is invalid: ${adapter.id}`);
|
|
49
|
+
}
|
|
50
|
+
const identity = pluginIdentity(this.ctx);
|
|
51
|
+
return registerPluginContribution(this.ctx, `llm.adapter:${adapter.id}`, () => {
|
|
52
|
+
const rootId = identity.scopeId;
|
|
53
|
+
const existing = this.adapters.get(rootId, adapter.id);
|
|
54
|
+
if (existing && existing.entryId !== identity.entryId) {
|
|
55
|
+
throw new MakaPluginRuntimeError('activation_failed', `LLM adapter is already registered in this scope: ${adapter.id}`);
|
|
56
|
+
}
|
|
57
|
+
const entry = {
|
|
58
|
+
...identity,
|
|
59
|
+
adapter,
|
|
60
|
+
token: Symbol(adapter.id),
|
|
61
|
+
retired: false,
|
|
62
|
+
};
|
|
63
|
+
return this.adapters.publish(rootId, adapter.id, entry);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
generate(input) {
|
|
67
|
+
const invocation = this.agents.requireInvocation();
|
|
68
|
+
const effectiveInput = Object.freeze({
|
|
69
|
+
...input,
|
|
70
|
+
signal: pluginInvocationSignal(invocation.abortSignal, input.signal),
|
|
71
|
+
});
|
|
72
|
+
const visibleAdapters = this.adapters.visible(invocation.sessionId);
|
|
73
|
+
const adapter = input.model
|
|
74
|
+
? [...visibleAdapters.values()]
|
|
75
|
+
.map((entry) => entry.adapter)
|
|
76
|
+
.filter((candidate) => candidate.supports(input.model))
|
|
77
|
+
.sort((left, right) => (right.priority ?? 0) - (left.priority ?? 0))[0]
|
|
78
|
+
: undefined;
|
|
79
|
+
if (adapter)
|
|
80
|
+
return adapter.generate(effectiveInput, invocation);
|
|
81
|
+
if (!this.llmRuntime)
|
|
82
|
+
throw new Error('Plugin LLM Runtime is unavailable');
|
|
83
|
+
return this.llmRuntime.generate(effectiveInput, invocation);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { Service } from './plugin-kernel.js';
|
|
20
|
+
import { PluginScopeRegistry } from './plugin-scope-registry.js';
|
|
21
|
+
import { pluginIdentity, registerPluginContribution, MakaPluginRuntimeError, } from './plugin-runtime.js';
|
|
22
|
+
/** High-level LSP router; providers own processes, callers receive no JSON-RPC escape hatch. */
|
|
23
|
+
export class PluginLspService extends Service {
|
|
24
|
+
routes = new PluginScopeRegistry();
|
|
25
|
+
constructor(ctx) {
|
|
26
|
+
super(ctx, 'lsp');
|
|
27
|
+
}
|
|
28
|
+
registerProvider(provider) {
|
|
29
|
+
validateProvider(provider);
|
|
30
|
+
const identity = pluginIdentity(this.ctx);
|
|
31
|
+
if (identity.scopeId === 'desktop-ui')
|
|
32
|
+
throw new MakaPluginRuntimeError('activation_failed', 'desktop-ui plugins cannot register Host LSP providers');
|
|
33
|
+
return registerPluginContribution(this.ctx, `lsp.registerProvider(${JSON.stringify(provider.id)})`, () => {
|
|
34
|
+
const rootId = identity.scopeId;
|
|
35
|
+
const pending = Object.entries(provider.extensionToLanguage).map(([extension, languageId]) => [normalizeExtension(extension), languageId]);
|
|
36
|
+
for (const [extension] of pending) {
|
|
37
|
+
const existing = this.routes.get(rootId, extension);
|
|
38
|
+
if (existing && existing.entryId !== identity.entryId)
|
|
39
|
+
throw new MakaPluginRuntimeError('activation_failed', `LSP extension ${JSON.stringify(extension)} is already registered by ${existing.entryId}`);
|
|
40
|
+
}
|
|
41
|
+
const disposers = pending.map(([extension, languageId]) => this.routes.publish(rootId, extension, {
|
|
42
|
+
...identity,
|
|
43
|
+
provider,
|
|
44
|
+
languageId,
|
|
45
|
+
token: Symbol(`${provider.id}:${extension}`),
|
|
46
|
+
retired: false,
|
|
47
|
+
}));
|
|
48
|
+
return async () => {
|
|
49
|
+
await Promise.all(disposers.reverse().map((dispose) => dispose()));
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
async query(request, signal) {
|
|
54
|
+
validateRequest(request);
|
|
55
|
+
const route = this.routes.visible(request.sessionId).get(finalExtension(request.filePath));
|
|
56
|
+
if (!route)
|
|
57
|
+
throw new Error(`No LSP provider handles ${JSON.stringify(request.filePath)}`);
|
|
58
|
+
if (signal?.aborted)
|
|
59
|
+
throw signal.reason ?? new Error('LSP query aborted');
|
|
60
|
+
return await route.provider.query(Object.freeze({ ...request, languageId: route.languageId }), signal);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export function finalExtension(filePath) {
|
|
64
|
+
const base = filePath.slice(Math.max(filePath.lastIndexOf('/'), filePath.lastIndexOf('\\')) + 1);
|
|
65
|
+
const dot = base.lastIndexOf('.');
|
|
66
|
+
return dot <= 0 ? '' : base.slice(dot).toLowerCase();
|
|
67
|
+
}
|
|
68
|
+
function normalizeExtension(value) {
|
|
69
|
+
const extension = value.toLowerCase();
|
|
70
|
+
const result = extension.startsWith('.') ? extension : `.${extension}`;
|
|
71
|
+
if (!/^\.[^./\\]+$/u.test(result))
|
|
72
|
+
throw new TypeError(`Invalid LSP extension: ${value}`);
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
function validateProvider(provider) {
|
|
76
|
+
if (!provider.id.trim() ||
|
|
77
|
+
typeof provider.query !== 'function' ||
|
|
78
|
+
Object.keys(provider.extensionToLanguage).length === 0)
|
|
79
|
+
throw new TypeError('Invalid LSP provider');
|
|
80
|
+
for (const [extension, language] of Object.entries(provider.extensionToLanguage)) {
|
|
81
|
+
normalizeExtension(extension);
|
|
82
|
+
if (!language.trim())
|
|
83
|
+
throw new TypeError('LSP language id must not be empty');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function validateRequest(request) {
|
|
87
|
+
if (!request.sessionId ||
|
|
88
|
+
!request.filePath ||
|
|
89
|
+
!Number.isSafeInteger(request.position.line) ||
|
|
90
|
+
request.position.line < 0 ||
|
|
91
|
+
!Number.isSafeInteger(request.position.character) ||
|
|
92
|
+
request.position.character < 0)
|
|
93
|
+
throw new TypeError('Invalid LSP query');
|
|
94
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Shared Profile/Session membership and replacement semantics for typed Plugin
|
|
21
|
+
* capabilities. Capability services retain validation and value-specific
|
|
22
|
+
* behavior; this registry only answers which keyed contribution is visible.
|
|
23
|
+
*/
|
|
24
|
+
export class PluginScopeRegistry {
|
|
25
|
+
#layers = new Map();
|
|
26
|
+
get(rootId, key) {
|
|
27
|
+
return this.#layers.get(rootId)?.get(key);
|
|
28
|
+
}
|
|
29
|
+
/** Snapshot Profile membership overlaid by an exact Session layer. */
|
|
30
|
+
visible(sessionId) {
|
|
31
|
+
if (!sessionId || /[\r\n\0]/u.test(sessionId)) {
|
|
32
|
+
throw new Error('Invalid Plugin Session scope');
|
|
33
|
+
}
|
|
34
|
+
const visible = new Map(this.#layers.get('profile'));
|
|
35
|
+
for (const [key, entry] of this.#layers.get(`session:${sessionId}`) ?? []) {
|
|
36
|
+
visible.set(key, entry);
|
|
37
|
+
}
|
|
38
|
+
return visible;
|
|
39
|
+
}
|
|
40
|
+
entries(rootId) {
|
|
41
|
+
return Object.freeze(rootId
|
|
42
|
+
? [...(this.#layers.get(rootId)?.values() ?? [])]
|
|
43
|
+
: [...this.#layers.values()].flatMap((layer) => [...layer.values()]));
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Atomically publishes one keyed contribution. A same-key predecessor is
|
|
47
|
+
* restored only when still live, which supports candidate rollback without
|
|
48
|
+
* resurrecting a successfully retired generation.
|
|
49
|
+
*/
|
|
50
|
+
publish(rootId, key, entry, hooks = {}) {
|
|
51
|
+
let layer = this.#layers.get(rootId);
|
|
52
|
+
if (!layer) {
|
|
53
|
+
layer = new Map();
|
|
54
|
+
this.#layers.set(rootId, layer);
|
|
55
|
+
}
|
|
56
|
+
const previous = layer.get(key);
|
|
57
|
+
layer.set(key, entry);
|
|
58
|
+
try {
|
|
59
|
+
hooks.onChanged?.(rootId);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (previous)
|
|
63
|
+
layer.set(key, previous);
|
|
64
|
+
else
|
|
65
|
+
layer.delete(key);
|
|
66
|
+
this.#prune(rootId);
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
let disposed = false;
|
|
70
|
+
return async () => {
|
|
71
|
+
if (disposed)
|
|
72
|
+
return;
|
|
73
|
+
disposed = true;
|
|
74
|
+
entry.retired = true;
|
|
75
|
+
const currentLayer = this.#layers.get(rootId);
|
|
76
|
+
if (currentLayer?.get(key)?.token === entry.token) {
|
|
77
|
+
if (previous && !previous.retired)
|
|
78
|
+
currentLayer.set(key, previous);
|
|
79
|
+
else
|
|
80
|
+
currentLayer.delete(key);
|
|
81
|
+
this.#prune(rootId);
|
|
82
|
+
hooks.onChanged?.(rootId);
|
|
83
|
+
}
|
|
84
|
+
await hooks.onRetired?.(entry);
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
#prune(rootId) {
|
|
88
|
+
if (this.#layers.get(rootId)?.size === 0)
|
|
89
|
+
this.#layers.delete(rootId);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { Service } from './plugin-kernel.js';
|
|
20
|
+
/** Read-only, paged Session projection. It never exposes the mutable Session Store. */
|
|
21
|
+
export class PluginSessionQueryService extends Service {
|
|
22
|
+
agents;
|
|
23
|
+
queryRuntime;
|
|
24
|
+
constructor(ctx, agents) {
|
|
25
|
+
super(ctx, 'sessionQuery');
|
|
26
|
+
this.agents = agents;
|
|
27
|
+
}
|
|
28
|
+
bindRuntime(runtime) {
|
|
29
|
+
if (this.ctx.maka)
|
|
30
|
+
throw new Error('Only the Host may bind the Session Query Runtime');
|
|
31
|
+
if (this.queryRuntime)
|
|
32
|
+
throw new Error('Plugin Session Query Runtime is already bound');
|
|
33
|
+
this.queryRuntime = runtime;
|
|
34
|
+
return this.ctx.effect(() => () => {
|
|
35
|
+
if (this.queryRuntime === runtime)
|
|
36
|
+
this.queryRuntime = undefined;
|
|
37
|
+
}, 'sessionQuery.bindRuntime()');
|
|
38
|
+
}
|
|
39
|
+
list() {
|
|
40
|
+
return this.runtime().list(this.caller());
|
|
41
|
+
}
|
|
42
|
+
read(sessionId) {
|
|
43
|
+
return this.runtime().read(assertSessionId(sessionId), this.caller());
|
|
44
|
+
}
|
|
45
|
+
search(request) {
|
|
46
|
+
if (!request.query.trim())
|
|
47
|
+
throw new TypeError('Session query must not be empty');
|
|
48
|
+
if (request.limit !== undefined &&
|
|
49
|
+
(!Number.isSafeInteger(request.limit) || request.limit < 1 || request.limit > 100)) {
|
|
50
|
+
throw new TypeError('Session query limit must be an integer from 1 to 100');
|
|
51
|
+
}
|
|
52
|
+
return this.runtime().search(Object.freeze({ ...request, query: request.query.trim() }), this.caller());
|
|
53
|
+
}
|
|
54
|
+
runtime() {
|
|
55
|
+
if (!this.queryRuntime)
|
|
56
|
+
throw new Error('Plugin Session Query Runtime is unavailable');
|
|
57
|
+
return this.queryRuntime;
|
|
58
|
+
}
|
|
59
|
+
caller() {
|
|
60
|
+
const invocation = this.agents.currentInvocation();
|
|
61
|
+
if (invocation)
|
|
62
|
+
return Object.freeze({ invocation });
|
|
63
|
+
const rootId = this.ctx.maka?.rootId;
|
|
64
|
+
return Object.freeze(rootId?.startsWith('session:') ? { scopeSessionId: rootId.slice('session:'.length) } : {});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function assertSessionId(value) {
|
|
68
|
+
if (!value || /[\0\r\n]/u.test(value))
|
|
69
|
+
throw new TypeError('Session id is invalid');
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { Service } from './plugin-kernel.js';
|
|
20
|
+
import { PluginScopeRegistry } from './plugin-scope-registry.js';
|
|
21
|
+
import { pluginIdentity, registerPluginContribution, MakaPluginRuntimeError, } from './plugin-runtime.js';
|
|
22
|
+
/** Deterministic, declared environment overlay rebuilt for each Shell execution. */
|
|
23
|
+
export class PluginShellEnvService extends Service {
|
|
24
|
+
variables = new PluginScopeRegistry();
|
|
25
|
+
constructor(ctx) {
|
|
26
|
+
super(ctx, 'shellEnv');
|
|
27
|
+
}
|
|
28
|
+
register(contributor) {
|
|
29
|
+
validateContributor(contributor);
|
|
30
|
+
const identity = pluginIdentity(this.ctx);
|
|
31
|
+
if (identity.scopeId === 'desktop-ui')
|
|
32
|
+
throw new MakaPluginRuntimeError('activation_failed', 'desktop-ui plugins cannot contribute Shell environment');
|
|
33
|
+
return registerPluginContribution(this.ctx, `shellEnv.register(${JSON.stringify(contributor.name)})`, () => {
|
|
34
|
+
const rootId = identity.scopeId;
|
|
35
|
+
const keys = Object.keys(contributor.variables).sort();
|
|
36
|
+
for (const key of keys) {
|
|
37
|
+
const existing = this.variables.get(rootId, key);
|
|
38
|
+
if (existing && existing.entryId !== identity.entryId)
|
|
39
|
+
throw new MakaPluginRuntimeError('activation_failed', `Shell environment key ${JSON.stringify(key)} is already registered by ${existing.entryId}`);
|
|
40
|
+
}
|
|
41
|
+
const disposers = keys.map((key) => this.variables.publish(rootId, key, {
|
|
42
|
+
...identity,
|
|
43
|
+
contributor,
|
|
44
|
+
key,
|
|
45
|
+
token: Symbol(`${contributor.name}:${key}`),
|
|
46
|
+
retired: false,
|
|
47
|
+
}));
|
|
48
|
+
return async () => {
|
|
49
|
+
await Promise.all(disposers.reverse().map((dispose) => dispose()));
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
async collect(invocation) {
|
|
54
|
+
const visible = this.variables.visible(invocation.sessionId);
|
|
55
|
+
const contributors = [
|
|
56
|
+
...new Set([...visible.values()].map(({ contributor }) => contributor)),
|
|
57
|
+
].sort((a, b) => a.name.localeCompare(b.name));
|
|
58
|
+
const output = Object.create(null);
|
|
59
|
+
for (const contributor of contributors) {
|
|
60
|
+
const resolved = await contributor.resolve(invocation);
|
|
61
|
+
for (const [key, value] of Object.entries(resolved)) {
|
|
62
|
+
if (!Object.hasOwn(contributor.variables, key))
|
|
63
|
+
throw new Error(`Shell environment contributor ${JSON.stringify(contributor.name)} returned undeclared key ${JSON.stringify(key)}`);
|
|
64
|
+
if (value !== undefined && typeof value !== 'string')
|
|
65
|
+
throw new TypeError(`Shell environment value ${JSON.stringify(key)} must be a string`);
|
|
66
|
+
if (value !== undefined)
|
|
67
|
+
output[key] = value;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return Object.freeze(Object.fromEntries(Object.entries(output).sort(([left], [right]) => left.localeCompare(right))));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function validateContributor(value) {
|
|
74
|
+
if (!/^[a-z][a-z0-9-]*$/u.test(value.name) || typeof value.resolve !== 'function')
|
|
75
|
+
throw new TypeError('Invalid Shell environment contributor');
|
|
76
|
+
const keys = Object.keys(value.variables);
|
|
77
|
+
if (keys.length === 0)
|
|
78
|
+
throw new TypeError('Shell environment contributor must declare at least one variable');
|
|
79
|
+
for (const key of keys) {
|
|
80
|
+
if (!/^MAKA_PLUGIN_[A-Z][A-Z0-9_]*$/u.test(key))
|
|
81
|
+
throw new TypeError(`Plugin Shell environment key is outside the MAKA_PLUGIN_* namespace: ${key}`);
|
|
82
|
+
if (!value.variables[key].description.trim())
|
|
83
|
+
throw new TypeError(`Plugin Shell environment key must have a description: ${key}`);
|
|
84
|
+
if (/^(?:PATH|HOME|SHELL|NODE_OPTIONS|LD_|DYLD_)/u.test(key.slice('MAKA_PLUGIN_'.length)))
|
|
85
|
+
throw new TypeError(`Plugin Shell environment key is reserved: ${key}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { Service } from './plugin-kernel.js';
|
|
20
|
+
/** Streaming, cancellable foreground/background/PTY shell surface. */
|
|
21
|
+
export class PluginShellService extends Service {
|
|
22
|
+
agents;
|
|
23
|
+
shellEnv;
|
|
24
|
+
shellRuntime;
|
|
25
|
+
constructor(ctx, agents, shellEnv) {
|
|
26
|
+
super(ctx, 'shell');
|
|
27
|
+
this.agents = agents;
|
|
28
|
+
this.shellEnv = shellEnv;
|
|
29
|
+
}
|
|
30
|
+
bindRuntime(runtime) {
|
|
31
|
+
if (this.ctx.maka)
|
|
32
|
+
throw new Error('Only the Host may bind the Shell Runtime');
|
|
33
|
+
if (this.shellRuntime)
|
|
34
|
+
throw new Error('Plugin Shell Runtime is already bound');
|
|
35
|
+
this.shellRuntime = runtime;
|
|
36
|
+
return this.ctx.effect(() => () => {
|
|
37
|
+
if (this.shellRuntime === runtime)
|
|
38
|
+
this.shellRuntime = undefined;
|
|
39
|
+
}, 'shell.bindRuntime()');
|
|
40
|
+
}
|
|
41
|
+
async run(options) {
|
|
42
|
+
if (options.environment !== undefined)
|
|
43
|
+
throw new TypeError('Shell environment is Host-managed');
|
|
44
|
+
const invocation = this.agents.requireInvocation();
|
|
45
|
+
const environment = await this.shellEnv?.collect(invocation);
|
|
46
|
+
return this.runtime().run(environment && Object.keys(environment).length > 0 ? { ...options, environment } : options, invocation);
|
|
47
|
+
}
|
|
48
|
+
read(ref) {
|
|
49
|
+
const invocation = this.agents.requireInvocation();
|
|
50
|
+
const read = this.runtime().read;
|
|
51
|
+
if (!read)
|
|
52
|
+
throw new Error('Shell resource reads are unavailable');
|
|
53
|
+
return read(ref, invocation);
|
|
54
|
+
}
|
|
55
|
+
write(ref, input) {
|
|
56
|
+
const invocation = this.agents.requireInvocation();
|
|
57
|
+
const write = this.runtime().write;
|
|
58
|
+
if (!write)
|
|
59
|
+
throw new Error('Shell PTY input is unavailable');
|
|
60
|
+
return write(ref, input, invocation);
|
|
61
|
+
}
|
|
62
|
+
stop(ref) {
|
|
63
|
+
const invocation = this.agents.requireInvocation();
|
|
64
|
+
const stop = this.runtime().stop;
|
|
65
|
+
if (!stop)
|
|
66
|
+
throw new Error('Shell process cancellation is unavailable');
|
|
67
|
+
return stop(ref, invocation);
|
|
68
|
+
}
|
|
69
|
+
runtime() {
|
|
70
|
+
if (!this.shellRuntime)
|
|
71
|
+
throw new Error('Plugin Shell Runtime is unavailable');
|
|
72
|
+
return this.shellRuntime;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { Service } from './plugin-kernel.js';
|
|
20
|
+
import { PluginScopeRegistry } from './plugin-scope-registry.js';
|
|
21
|
+
import { pluginIdentity, registerPluginContribution, MakaPluginRuntimeError, } from './plugin-runtime.js';
|
|
22
|
+
/** Profile/Session-scoped Skill contributions owned by the registering Fiber. */
|
|
23
|
+
export class PluginSkillService extends Service {
|
|
24
|
+
registry = new PluginScopeRegistry();
|
|
25
|
+
revision = 0;
|
|
26
|
+
constructor(ctx) {
|
|
27
|
+
super(ctx, 'skills');
|
|
28
|
+
}
|
|
29
|
+
register(definition) {
|
|
30
|
+
validateSkill(definition);
|
|
31
|
+
const identity = pluginIdentity(this.ctx);
|
|
32
|
+
if (identity.scopeId === 'desktop-ui')
|
|
33
|
+
throw new MakaPluginRuntimeError('activation_failed', 'desktop-ui plugins cannot register Host skills');
|
|
34
|
+
return registerPluginContribution(this.ctx, `skills.register(${JSON.stringify(definition.name)})`, () => {
|
|
35
|
+
const rootId = identity.scopeId;
|
|
36
|
+
const existing = this.registry.get(rootId, definition.name);
|
|
37
|
+
if (existing && existing.entryId !== identity.entryId)
|
|
38
|
+
throw new MakaPluginRuntimeError('activation_failed', `Plugin Skill ${JSON.stringify(definition.name)} is already registered by ${existing.entryId}`);
|
|
39
|
+
const dispose = this.registry.publish(rootId, definition.name, {
|
|
40
|
+
...identity,
|
|
41
|
+
definition: freezeSkill(definition),
|
|
42
|
+
token: Symbol(definition.name),
|
|
43
|
+
retired: false,
|
|
44
|
+
});
|
|
45
|
+
this.revision += 1;
|
|
46
|
+
return async () => {
|
|
47
|
+
await dispose();
|
|
48
|
+
this.revision += 1;
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
resolve(sessionId) {
|
|
53
|
+
return Object.freeze([...this.registry.visible(assertScope(sessionId)).values()]
|
|
54
|
+
.sort(compareIdentity)
|
|
55
|
+
.map(({ definition }) => definition));
|
|
56
|
+
}
|
|
57
|
+
get(sessionId, name) {
|
|
58
|
+
return this.registry.visible(assertScope(sessionId)).get(name)?.definition;
|
|
59
|
+
}
|
|
60
|
+
snapshot(sessionId) {
|
|
61
|
+
return Object.freeze({ revision: this.revision, skills: this.resolve(sessionId) });
|
|
62
|
+
}
|
|
63
|
+
inspect(rootId) {
|
|
64
|
+
return Object.freeze([...this.registry.entries(rootId)]
|
|
65
|
+
.sort(compareIdentity)
|
|
66
|
+
.map(({ definition, ...identity }) => Object.freeze({ ...identity, name: definition.name })));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function validateSkill(value) {
|
|
70
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(value.name))
|
|
71
|
+
throw new TypeError('Plugin Skill name must be lower-kebab-case');
|
|
72
|
+
if (!value.description.trim() || !value.instructions.trim())
|
|
73
|
+
throw new TypeError('Plugin Skill description and instructions are required');
|
|
74
|
+
if (value.instructions.length > 256 * 1024)
|
|
75
|
+
throw new TypeError('Plugin Skill instructions exceed 256 KiB');
|
|
76
|
+
}
|
|
77
|
+
function freezeSkill(value) {
|
|
78
|
+
return Object.freeze({
|
|
79
|
+
...value,
|
|
80
|
+
description: value.description.trim(),
|
|
81
|
+
instructions: value.instructions,
|
|
82
|
+
declaredTools: Object.freeze([...(value.declaredTools ?? [])]),
|
|
83
|
+
requiredTools: Object.freeze([...(value.requiredTools ?? [])]),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function assertScope(value) {
|
|
87
|
+
if (!value || /[\0\r\n]/u.test(value))
|
|
88
|
+
throw new TypeError('Session id is invalid');
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
function compareIdentity(left, right) {
|
|
92
|
+
return left.entryId.localeCompare(right.entryId) || left.generation - right.generation;
|
|
93
|
+
}
|