maka-agent 0.2.0-dev.26.20260909 → 0.2.0-dev.27.20260910

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.
Files changed (62) hide show
  1. package/dist/cli-core.js +21 -0
  2. package/dist/runtime-host-cli.js +43 -3
  3. package/dist/runtime-host-handoff-surface.js +10 -8
  4. package/dist/runtime-host-lifecycle-transaction.js +28 -3
  5. package/dist/runtime-host-local-source-retirement.js +1 -1
  6. package/dist/runtime-host-setup-command.js +29 -6
  7. package/dist/runtime-host-update-command.js +49 -5
  8. package/dist/session-export-command.js +72 -0
  9. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  10. package/node_modules/@maka/core/dist/usage-stats/types.js +2 -0
  11. package/node_modules/@maka/core/dist/workhub-routing.js +126 -0
  12. package/node_modules/@maka/core/package.json +2 -1
  13. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +7 -3
  14. package/node_modules/@maka/runtime/dist/builtin-tools.js +21 -2
  15. package/node_modules/@maka/runtime/dist/plugin-agent-service.js +131 -0
  16. package/node_modules/@maka/runtime/dist/plugin-approval-service.js +33 -0
  17. package/node_modules/@maka/runtime/dist/plugin-attachment-service.js +53 -0
  18. package/node_modules/@maka/runtime/dist/plugin-command-service.js +100 -0
  19. package/node_modules/@maka/runtime/dist/plugin-data-services.js +332 -0
  20. package/node_modules/@maka/runtime/dist/plugin-fs-service.js +62 -0
  21. package/node_modules/@maka/runtime/dist/plugin-goal-service.js +61 -0
  22. package/node_modules/@maka/runtime/dist/plugin-invocation-signal.js +24 -0
  23. package/node_modules/@maka/runtime/dist/plugin-llm-service.js +85 -0
  24. package/node_modules/@maka/runtime/dist/plugin-lsp-service.js +94 -0
  25. package/node_modules/@maka/runtime/dist/plugin-scope-registry.js +91 -0
  26. package/node_modules/@maka/runtime/dist/plugin-session-query-service.js +71 -0
  27. package/node_modules/@maka/runtime/dist/plugin-shell-env-service.js +87 -0
  28. package/node_modules/@maka/runtime/dist/plugin-shell-service.js +74 -0
  29. package/node_modules/@maka/runtime/dist/plugin-skill-service.js +93 -0
  30. package/node_modules/@maka/runtime/dist/plugin-system-prompt-service.js +346 -0
  31. package/node_modules/@maka/runtime/dist/plugin-tool-service.js +18 -52
  32. package/node_modules/@maka/runtime/dist/plugin-user-question-service.js +43 -0
  33. package/node_modules/@maka/runtime/dist/plugin-web-service.js +69 -0
  34. package/node_modules/@maka/runtime/dist/session-export.js +177 -0
  35. package/node_modules/@maka/runtime/package.json +18 -0
  36. package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +67 -18
  37. package/node_modules/@maka/runtime-host/dist/client/host-handoff.js +46 -12
  38. package/node_modules/@maka/runtime-host/dist/operator/setup-frame.js +31 -21
  39. package/node_modules/@maka/runtime-host/dist/protocol/index.js +9 -1
  40. package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +30 -5
  41. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +25 -23
  42. package/node_modules/@maka/runtime-host/dist/server/client-capability-coordinator.js +62 -4
  43. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +452 -7
  44. package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +74 -0
  45. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +2 -2
  46. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +30 -0
  47. package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +79 -16
  48. package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +25 -4
  49. package/node_modules/@maka/runtime-host/dist/server/plugin-data-runtime.js +234 -0
  50. package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +6 -0
  51. package/node_modules/@maka/runtime-host/dist/server/plugin-platform.js +12 -0
  52. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +189 -92
  53. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +44 -35
  54. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +92 -10
  55. package/node_modules/@maka/storage/dist/agent-run-store.js +54 -10
  56. package/node_modules/@maka/storage/dist/context-offload-snapshot.js +13 -6
  57. package/node_modules/@maka/storage/dist/execution-stores.js +1 -1
  58. package/node_modules/@maka/storage/dist/session-bundle-policy.js +333 -30
  59. package/node_modules/@maka/storage/dist/session-store.js +2 -2
  60. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +12 -5
  61. package/node_modules/@maka/storage/package.json +2 -0
  62. package/package.json +1 -1
@@ -0,0 +1,332 @@
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 { pluginIdentity, registerPluginContribution } from './plugin-runtime.js';
21
+ class PluginNamespacedDataService extends Service {
22
+ domain;
23
+ dataRuntime;
24
+ constructor(ctx, name, domain) {
25
+ super(ctx, name);
26
+ this.domain = domain;
27
+ }
28
+ bindRuntime(runtime) {
29
+ if (this.ctx.maka)
30
+ throw new Error(`Only the Host may bind the Plugin ${this.domain} Runtime`);
31
+ if (this.dataRuntime)
32
+ throw new Error(`Plugin ${this.domain} Runtime is already bound`);
33
+ this.dataRuntime = runtime;
34
+ return this.ctx.effect(() => () => {
35
+ if (this.dataRuntime === runtime)
36
+ this.dataRuntime = undefined;
37
+ }, `${this.domain}.bindRuntime()`);
38
+ }
39
+ runtime() {
40
+ if (!this.dataRuntime)
41
+ throw new Error(`Plugin ${this.domain} Runtime is unavailable`);
42
+ return this.dataRuntime;
43
+ }
44
+ namespace() {
45
+ const { extensionId, scopeId } = pluginIdentity(this.ctx);
46
+ return Object.freeze({ extensionId, scopeId });
47
+ }
48
+ readValue(key) {
49
+ return this.runtime().read(this.namespace(), this.domain, validKey(key));
50
+ }
51
+ listValues(prefix) {
52
+ return this.runtime().list(this.namespace(), this.domain, prefix === undefined ? undefined : validPrefix(prefix));
53
+ }
54
+ mutateValues(mutations) {
55
+ return this.runtime().mutate(this.namespace(), this.domain, Object.freeze(mutations.map((mutation) => Object.freeze({
56
+ ...mutation,
57
+ key: validKey(mutation.key),
58
+ ...(mutation.value === undefined ? {} : { value: detached(mutation.value) }),
59
+ }))));
60
+ }
61
+ watchValues(listener) {
62
+ const subscribe = this.runtime().subscribe;
63
+ if (!subscribe)
64
+ throw new Error(`Plugin ${this.domain} subscriptions are unavailable`);
65
+ return subscribe(this.namespace(), this.domain, listener);
66
+ }
67
+ }
68
+ /** Per-extension Settings with schema contributions, CAS revisions, and no global-policy access. */
69
+ export class PluginSettingsService extends PluginNamespacedDataService {
70
+ definitions = new Map();
71
+ constructor(ctx) {
72
+ super(ctx, 'settings', 'settings');
73
+ }
74
+ define(definition) {
75
+ validateSettingDefinition(definition);
76
+ const identity = pluginIdentity(this.ctx);
77
+ return registerPluginContribution(this.ctx, `settings.define(${JSON.stringify(definition.key)})`, () => {
78
+ const id = `${identity.scopeId}\0${identity.extensionId}\0${definition.key}`;
79
+ if (this.definitions.has(id))
80
+ throw new Error(`Plugin Setting is already defined: ${definition.key}`);
81
+ this.definitions.set(id, Object.freeze({
82
+ ...definition,
83
+ ...(definition.defaultValue === undefined
84
+ ? {}
85
+ : { defaultValue: detached(definition.defaultValue) }),
86
+ }));
87
+ return () => {
88
+ this.definitions.delete(id);
89
+ };
90
+ });
91
+ }
92
+ async get(key) {
93
+ const snapshot = await this.readValue(key);
94
+ if (snapshot.value !== undefined)
95
+ return snapshot;
96
+ const definition = this.definition(key);
97
+ return definition?.defaultValue === undefined
98
+ ? snapshot
99
+ : Object.freeze({
100
+ revision: snapshot.revision,
101
+ value: detached(definition.defaultValue),
102
+ });
103
+ }
104
+ async set(key, value, options = {}) {
105
+ const definition = this.definition(key);
106
+ if (!definition)
107
+ throw new Error(`Plugin Setting is not defined: ${key}`);
108
+ if (definition.secret)
109
+ throw new Error(`Secret Setting must use ctx.authorization: ${key}`);
110
+ if (definition.validate && !definition.validate(value))
111
+ throw new TypeError(`Plugin Setting failed validation: ${key}`);
112
+ const result = await this.mutateValues([{ key, value, ...options }]);
113
+ return result[key];
114
+ }
115
+ async delete(key, options = {}) {
116
+ const result = await this.mutateValues([{ key, ...options }]);
117
+ return result[key];
118
+ }
119
+ list(prefix) {
120
+ return this.listValues(prefix);
121
+ }
122
+ watch(listener) {
123
+ return this.watchValues(listener);
124
+ }
125
+ definition(key) {
126
+ const identity = pluginIdentity(this.ctx);
127
+ return this.definitions.get(`${identity.scopeId}\0${identity.extensionId}\0${validKey(key)}`);
128
+ }
129
+ }
130
+ /** Per-extension durable KV/blob-safe JSON surface with atomic batch mutations. */
131
+ export class PluginStorageService extends PluginNamespacedDataService {
132
+ constructor(ctx) {
133
+ super(ctx, 'storage', 'storage');
134
+ }
135
+ get(key) {
136
+ return this.readValue(key);
137
+ }
138
+ async set(key, value, options = {}) {
139
+ const result = await this.mutateValues([{ key, value, ...options }]);
140
+ return result[key];
141
+ }
142
+ async delete(key, options = {}) {
143
+ const result = await this.mutateValues([{ key, ...options }]);
144
+ return result[key];
145
+ }
146
+ list(prefix) {
147
+ return this.listValues(prefix);
148
+ }
149
+ transaction(mutations) {
150
+ if (!mutations.length)
151
+ throw new TypeError('Plugin Storage transaction must not be empty');
152
+ return this.mutateValues(mutations);
153
+ }
154
+ watch(listener) {
155
+ return this.watchValues(listener);
156
+ }
157
+ }
158
+ const authorizationCommit = Symbol('authorizationCommit');
159
+ /** Declared Secret Slots. Values stay in Host custody and are never enumerable. */
160
+ export class PluginCredentialService extends Service {
161
+ dataRuntime;
162
+ slots = new Map();
163
+ constructor(ctx) {
164
+ super(ctx, 'credentials');
165
+ }
166
+ bindRuntime(runtime) {
167
+ if (this.ctx.maka)
168
+ throw new Error('Only the Host may bind the Credential Runtime');
169
+ if (this.dataRuntime)
170
+ throw new Error('Plugin Credential Runtime is already bound');
171
+ this.dataRuntime = runtime;
172
+ return this.ctx.effect(() => () => {
173
+ if (this.dataRuntime === runtime)
174
+ this.dataRuntime = undefined;
175
+ }, 'credentials.bindRuntime()');
176
+ }
177
+ declare(slot) {
178
+ validateSlot(slot);
179
+ const identity = namespace(this.ctx);
180
+ const id = slotId(identity, slot.name);
181
+ return registerPluginContribution(this.ctx, `credentials.declare(${JSON.stringify(slot.name)})`, () => {
182
+ if (this.slots.has(id))
183
+ throw new Error(`Credential Slot is already declared: ${slot.name}`);
184
+ this.slots.set(id, { identity, definition: Object.freeze({ ...slot }) });
185
+ return () => {
186
+ this.slots.delete(id);
187
+ };
188
+ });
189
+ }
190
+ has(name) {
191
+ const slot = this.requireSlot(name);
192
+ return this.runtime().hasCredential(slot.identity, slot.definition.name);
193
+ }
194
+ use(name, operation) {
195
+ if (typeof operation !== 'function')
196
+ throw new TypeError('Credential use callback is required');
197
+ const slot = this.requireSlot(name);
198
+ return this.runtime().useCredential(slot.identity, slot.definition.name, operation);
199
+ }
200
+ remove(name) {
201
+ const slot = this.requireSlot(name);
202
+ return this.runtime().removeCredential(slot.identity, slot.definition.name);
203
+ }
204
+ async [authorizationCommit](name, secret, metadata) {
205
+ if (!secret)
206
+ throw new TypeError('Credential secret must not be empty');
207
+ const slot = this.requireSlot(name);
208
+ await this.runtime().commitCredential(slot.identity, slot.definition.name, secret, metadata);
209
+ }
210
+ requireSlot(name) {
211
+ const identity = namespace(this.ctx);
212
+ const slot = this.slots.get(slotId(identity, validName(name)));
213
+ if (!slot)
214
+ throw new Error(`Credential Slot is not declared: ${name}`);
215
+ return slot;
216
+ }
217
+ runtime() {
218
+ if (!this.dataRuntime)
219
+ throw new Error('Plugin Credential Runtime is unavailable');
220
+ return this.dataRuntime;
221
+ }
222
+ }
223
+ /** Effect-owned authorization flows with one cancellable attempt per Secret Slot. */
224
+ export class PluginAuthorizationService extends Service {
225
+ flows = new Map();
226
+ active = new Map();
227
+ constructor(ctx, _credentials) {
228
+ super(ctx, 'authorization');
229
+ }
230
+ register(flow) {
231
+ validateFlow(flow);
232
+ const identity = namespace(this.ctx);
233
+ const id = slotId(identity, flow.slot);
234
+ return registerPluginContribution(this.ctx, `authorization.register(${JSON.stringify(flow.slot)})`, () => {
235
+ if (this.flows.has(id))
236
+ throw new Error(`Authorization Flow is already registered: ${flow.slot}`);
237
+ this.flows.set(id, flow);
238
+ return () => {
239
+ this.active.get(id)?.abort(new Error('Authorization Flow disposed'));
240
+ this.active.delete(id);
241
+ this.flows.delete(id);
242
+ };
243
+ });
244
+ }
245
+ async begin(slot, method, signal) {
246
+ const identity = namespace(this.ctx);
247
+ const id = slotId(identity, validName(slot));
248
+ const flow = this.flows.get(id);
249
+ if (!flow)
250
+ throw new Error(`Authorization Flow is unavailable: ${slot}`);
251
+ if (this.active.has(id))
252
+ throw new Error(`Authorization is already in progress: ${slot}`);
253
+ const selected = method ?? flow.methods[0].id;
254
+ if (!flow.methods.some(({ id }) => id === selected))
255
+ throw new Error(`Authorization method is unavailable: ${selected}`);
256
+ const controller = new AbortController();
257
+ const onAbort = () => controller.abort(signal?.reason);
258
+ signal?.addEventListener('abort', onAbort, { once: true });
259
+ this.active.set(id, controller);
260
+ let committed = false;
261
+ try {
262
+ const outcome = await flow.run({
263
+ method: selected,
264
+ signal: controller.signal,
265
+ commit: async (secret, metadata) => {
266
+ await this.ctx.credentials[authorizationCommit](slot, secret, metadata);
267
+ committed = true;
268
+ },
269
+ });
270
+ if (outcome === 'authorized' && !committed)
271
+ throw new Error('Authorization Flow returned authorized without committing a credential');
272
+ return outcome;
273
+ }
274
+ finally {
275
+ signal?.removeEventListener('abort', onAbort);
276
+ if (this.active.get(id) === controller)
277
+ this.active.delete(id);
278
+ }
279
+ }
280
+ cancel(slot) {
281
+ const identity = namespace(this.ctx);
282
+ this.active.get(slotId(identity, validName(slot)))?.abort(new Error('Authorization cancelled'));
283
+ }
284
+ }
285
+ function namespace(ctx) {
286
+ const { extensionId, scopeId } = pluginIdentity(ctx);
287
+ return Object.freeze({ extensionId, scopeId });
288
+ }
289
+ function slotId(identity, name) {
290
+ return `${identity.scopeId}\0${identity.extensionId}\0${name}`;
291
+ }
292
+ function validName(value) {
293
+ if (!/^[a-z][a-z0-9]*(?:[-_.][a-z0-9]+)*$/u.test(value))
294
+ throw new TypeError(`Invalid plugin name: ${value}`);
295
+ return value;
296
+ }
297
+ function validKey(value) {
298
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9._/-]{0,255}$/u.test(value) || value.includes('..'))
299
+ throw new TypeError(`Invalid plugin data key: ${value}`);
300
+ return value;
301
+ }
302
+ function validPrefix(value) {
303
+ if (value === '')
304
+ return value;
305
+ return validKey(value);
306
+ }
307
+ function detached(value) {
308
+ const json = JSON.stringify(value);
309
+ if (json === undefined)
310
+ throw new TypeError('Plugin data must be JSON-serializable');
311
+ if (Buffer.byteLength(json, 'utf8') > 1024 * 1024)
312
+ throw new TypeError('Plugin data value exceeds 1 MiB');
313
+ return JSON.parse(json);
314
+ }
315
+ function validateSettingDefinition(value) {
316
+ validKey(value.key);
317
+ if (!value.title.trim())
318
+ throw new TypeError('Plugin Setting title must not be empty');
319
+ }
320
+ function validateSlot(value) {
321
+ validName(value.name);
322
+ if (!value.label.trim())
323
+ throw new TypeError('Credential Slot label must not be empty');
324
+ }
325
+ function validateFlow(value) {
326
+ validName(value.slot);
327
+ if (!value.label.trim() || typeof value.run !== 'function' || value.methods.length === 0)
328
+ throw new TypeError('Invalid Authorization Flow');
329
+ const ids = value.methods.map(({ id }) => validName(id));
330
+ if (new Set(ids).size !== ids.length || value.methods.some(({ label }) => !label.trim()))
331
+ throw new TypeError('Invalid Authorization methods');
332
+ }
@@ -0,0 +1,62 @@
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
+ /** Full-fidelity filesystem entry point bound to Maka's canonical workspace authority. */
21
+ export class PluginFilesystemService extends Service {
22
+ agents;
23
+ filesystemRuntime;
24
+ constructor(ctx, agents) {
25
+ super(ctx, 'fs');
26
+ this.agents = agents;
27
+ }
28
+ bindRuntime(runtime) {
29
+ if (this.ctx.maka)
30
+ throw new Error('Only the Host may bind the Filesystem Runtime');
31
+ if (this.filesystemRuntime)
32
+ throw new Error('Plugin Filesystem Runtime is already bound');
33
+ this.filesystemRuntime = runtime;
34
+ return this.ctx.effect(() => () => {
35
+ if (this.filesystemRuntime === runtime)
36
+ this.filesystemRuntime = undefined;
37
+ }, 'fs.bindRuntime()');
38
+ }
39
+ execute(operation) {
40
+ if (!this.filesystemRuntime)
41
+ throw new Error('Plugin Filesystem Runtime is unavailable');
42
+ return this.filesystemRuntime.execute(operation, this.agents.requireInvocation());
43
+ }
44
+ read(path, options = {}) {
45
+ return this.execute({ kind: 'read', path, ...options });
46
+ }
47
+ write(path, content) {
48
+ return this.execute({ kind: 'write', path, content });
49
+ }
50
+ edit(path, oldString, newString) {
51
+ return this.execute({ kind: 'edit', path, oldString, newString });
52
+ }
53
+ glob(pattern, options = {}) {
54
+ return this.execute({ kind: 'glob', pattern, ...options });
55
+ }
56
+ grep(pattern, options = {}) {
57
+ return this.execute({ kind: 'grep', pattern, ...options });
58
+ }
59
+ applyPatch(patch) {
60
+ return this.execute({ kind: 'apply_patch', patch });
61
+ }
62
+ }
@@ -0,0 +1,61 @@
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
+ /** Current-Session Goal facade; mutations retain Maka's Turn lease and revision authority. */
21
+ export class PluginGoalService extends Service {
22
+ agents;
23
+ goalRuntime;
24
+ constructor(ctx, agents) {
25
+ super(ctx, 'goals');
26
+ this.agents = agents;
27
+ }
28
+ bindRuntime(runtime) {
29
+ if (this.ctx.maka)
30
+ throw new Error('Only the Host may bind the Goal Runtime');
31
+ if (this.goalRuntime)
32
+ throw new Error('Plugin Goal Runtime is already bound');
33
+ this.goalRuntime = runtime;
34
+ return this.ctx.effect(() => () => {
35
+ if (this.goalRuntime === runtime)
36
+ this.goalRuntime = undefined;
37
+ }, 'goals.bindRuntime()');
38
+ }
39
+ get() {
40
+ return this.execute({ kind: 'get' });
41
+ }
42
+ create(input) {
43
+ if (!input.objective.trim())
44
+ throw new TypeError('Goal objective must not be empty');
45
+ return this.execute({ ...input, kind: 'create', objective: input.objective.trim() });
46
+ }
47
+ clear() {
48
+ return this.execute({ kind: 'clear' });
49
+ }
50
+ pause() {
51
+ return this.execute({ kind: 'pause' });
52
+ }
53
+ resume() {
54
+ return this.execute({ kind: 'resume' });
55
+ }
56
+ execute(operation) {
57
+ if (!this.goalRuntime)
58
+ throw new Error('Plugin Goal Runtime is unavailable');
59
+ return this.goalRuntime.execute(operation, this.agents.requireInvocation());
60
+ }
61
+ }
@@ -0,0 +1,24 @@
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
+ /** Preserve Host cancellation when a plugin adds its own cancellation source. */
20
+ export function pluginInvocationSignal(invocationSignal, pluginSignal) {
21
+ if (!pluginSignal || pluginSignal === invocationSignal)
22
+ return invocationSignal;
23
+ return AbortSignal.any([invocationSignal, pluginSignal]);
24
+ }
@@ -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
+ }