maka-agent 0.2.0-dev.67.20261002 → 0.2.0-dev.69.20261004
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/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-catalog.js +132 -0
- package/node_modules/@maka/acp-executor-plugin/dist/index.js +251 -113
- package/node_modules/@maka/acp-executor-plugin/dist/plugin.mjs +431 -108
- package/node_modules/@maka/core/dist/executor-catalog.js +25 -3
- package/node_modules/@maka/core/dist/recall.js +13 -14
- package/node_modules/@maka/runtime/dist/admission-limiter.js +3 -2
- package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +0 -3
- package/node_modules/@maka/runtime/dist/model-history.js +4 -14
- package/node_modules/@maka/runtime/dist/plugin-data-services.js +7 -0
- package/node_modules/@maka/runtime/dist/plugin-executor-service.js +19 -3
- package/node_modules/@maka/runtime/dist/runtime-read-model.js +23 -4
- package/node_modules/@maka/runtime/dist/session-manager.js +1 -0
- package/node_modules/@maka/runtime/package.json +1 -0
- package/node_modules/@maka/runtime-host/dist/antigravity-program.js +49 -0
- package/node_modules/@maka/runtime-host/dist/protocol/index.js +3 -1
- package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +8 -2
- package/node_modules/@maka/runtime-host/dist/server/acp/antigravity.js +5 -19
- package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +14 -3
- package/node_modules/@maka/runtime-host/dist/server/external-agent-setup-coordinator.js +3 -0
- package/node_modules/@maka/runtime-host/dist/server/plugin-data-runtime.js +6 -0
- package/node_modules/@maka/runtime-host/dist/server/plugin-scratch-directory.js +55 -0
- package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +40 -6
- package/node_modules/@maka/runtime-host/package.json +1 -0
- package/node_modules/@maka/storage/dist/execution-stores.js +10 -0
- package/node_modules/@maka/storage/dist/runtime-event-persistence.js +1 -0
- package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +74 -5
- package/package.json +1 -1
|
@@ -23,12 +23,17 @@ export function isExecutorConfiguration(value) {
|
|
|
23
23
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
24
24
|
return false;
|
|
25
25
|
const record = value;
|
|
26
|
-
return (Object.keys(record).every((key) => key === 'model') &&
|
|
26
|
+
return (Object.keys(record).every((key) => key === 'model' || key === 'mode') &&
|
|
27
27
|
(record.model === undefined ||
|
|
28
28
|
(typeof record.model === 'string' &&
|
|
29
29
|
record.model.length > 0 &&
|
|
30
30
|
record.model.length <= 1024 &&
|
|
31
|
-
!/[\0\r\n]/u.test(record.model)))
|
|
31
|
+
!/[\0\r\n]/u.test(record.model))) &&
|
|
32
|
+
(record.mode === undefined ||
|
|
33
|
+
(typeof record.mode === 'string' &&
|
|
34
|
+
record.mode.length > 0 &&
|
|
35
|
+
record.mode.length <= 1024 &&
|
|
36
|
+
!/[\0\r\n]/u.test(record.mode))));
|
|
32
37
|
}
|
|
33
38
|
export function normalizeCatalogEntry(value, id) {
|
|
34
39
|
if (!value ||
|
|
@@ -54,8 +59,18 @@ export function normalizeCatalogEntry(value, id) {
|
|
|
54
59
|
(model.providerType === undefined || CATALOG_PROVIDER_TYPES.includes(model.providerType))) ||
|
|
55
60
|
new Set(value.models.map((model) => model.id)).size !== value.models.length ||
|
|
56
61
|
!isExecutorConfiguration({ model: value.currentModel }) ||
|
|
62
|
+
(value.modes !== undefined &&
|
|
63
|
+
(!Array.isArray(value.modes) ||
|
|
64
|
+
value.modes.length > 64 ||
|
|
65
|
+
!value.modes.every((mode) => mode &&
|
|
66
|
+
typeof mode.id === 'string' &&
|
|
67
|
+
isExecutorConfiguration({ mode: mode.id }) &&
|
|
68
|
+
isCatalogText(mode.name)) ||
|
|
69
|
+
new Set(value.modes.map((mode) => mode.id)).size !== value.modes.length)) ||
|
|
70
|
+
!isExecutorConfiguration({ mode: value.currentMode }) ||
|
|
57
71
|
typeof value.supportsAttachments !== 'boolean' ||
|
|
58
|
-
typeof value.supportsModelChange !== 'boolean'
|
|
72
|
+
typeof value.supportsModelChange !== 'boolean' ||
|
|
73
|
+
(value.supportsModeChange !== undefined && typeof value.supportsModeChange !== 'boolean'))
|
|
59
74
|
throw new TypeError('Executor catalog is invalid');
|
|
60
75
|
const usedModels = new Set();
|
|
61
76
|
const groupIds = new Set();
|
|
@@ -106,8 +121,15 @@ export function normalizeCatalogEntry(value, id) {
|
|
|
106
121
|
}
|
|
107
122
|
: {}),
|
|
108
123
|
...(value.currentModel !== undefined ? { currentModel: value.currentModel } : {}),
|
|
124
|
+
...(value.modes !== undefined
|
|
125
|
+
? { modes: Object.freeze(value.modes.map(({ id, name }) => Object.freeze({ id, name }))) }
|
|
126
|
+
: {}),
|
|
127
|
+
...(value.currentMode !== undefined ? { currentMode: value.currentMode } : {}),
|
|
109
128
|
supportsAttachments: value.supportsAttachments,
|
|
110
129
|
supportsModelChange: value.supportsModelChange,
|
|
130
|
+
...(value.supportsModeChange !== undefined
|
|
131
|
+
? { supportsModeChange: value.supportsModeChange }
|
|
132
|
+
: {}),
|
|
111
133
|
});
|
|
112
134
|
}
|
|
113
135
|
function isCatalogText(value) {
|
|
@@ -682,6 +682,18 @@ async function collectHits(deps, input) {
|
|
|
682
682
|
scannedFully = false;
|
|
683
683
|
}
|
|
684
684
|
}
|
|
685
|
+
// A narrowed scan needs the corpus count to rank the same way as a full
|
|
686
|
+
// scan. Resolve it before reading transcripts so a declined count cannot
|
|
687
|
+
// discard work and reread every candidate during fallback.
|
|
688
|
+
const storedCorpusSize = deps.countSearchableMessages
|
|
689
|
+
? await deps.countSearchableMessages({ sessionIds: input.sessionIds })
|
|
690
|
+
: null;
|
|
691
|
+
if (input.abortSignal?.aborted)
|
|
692
|
+
return null;
|
|
693
|
+
if (storedCorpusSize === null && !scannedFully) {
|
|
694
|
+
sessionIds = input.sessionIds;
|
|
695
|
+
scannedFully = true;
|
|
696
|
+
}
|
|
685
697
|
const hits = [];
|
|
686
698
|
let counted = 0;
|
|
687
699
|
for (const sessionId of sessionIds) {
|
|
@@ -711,20 +723,7 @@ async function collectHits(deps, input) {
|
|
|
711
723
|
hits.push(hit);
|
|
712
724
|
}
|
|
713
725
|
}
|
|
714
|
-
|
|
715
|
-
// has to come from the store. The full scan takes the same number when the
|
|
716
|
-
// store offers one, which is what keeps a score independent of the path.
|
|
717
|
-
let corpusSize = counted;
|
|
718
|
-
if (deps.countSearchableMessages) {
|
|
719
|
-
corpusSize = await deps.countSearchableMessages({ sessionIds: input.sessionIds });
|
|
720
|
-
if (input.abortSignal?.aborted)
|
|
721
|
-
return null;
|
|
722
|
-
if (corpusSize === null) {
|
|
723
|
-
if (!scannedFully)
|
|
724
|
-
return collectHits(deps, { ...input, forceFullScan: true });
|
|
725
|
-
corpusSize = counted;
|
|
726
|
-
}
|
|
727
|
-
}
|
|
726
|
+
const corpusSize = storedCorpusSize ?? counted;
|
|
728
727
|
return { hits, corpusSize, scannedFully, transcripts };
|
|
729
728
|
}
|
|
730
729
|
/**
|
|
@@ -19,12 +19,13 @@
|
|
|
19
19
|
/**
|
|
20
20
|
* Abort-aware FIFO permits, bounded by capacity.
|
|
21
21
|
*
|
|
22
|
-
*
|
|
22
|
+
* Boundaries use it at different lifetimes. Child-agent runs take one
|
|
23
23
|
* instance per turn: tool-call admission and child-run capacity are separate
|
|
24
24
|
* boundaries, since one admitted tool may eventually spawn multiple children,
|
|
25
25
|
* so the limiter belongs at the narrow spawn capability where every caller
|
|
26
26
|
* shares the same real-run budget. Code Mode cells take one per backend, which
|
|
27
|
-
* has to outlive a turn — see `executeCodeModeCell`.
|
|
27
|
+
* has to outlive a turn — see `executeCodeModeCell`. ACP catalog probes share
|
|
28
|
+
* one per runtime Entry and hold permits through process cleanup.
|
|
28
29
|
*
|
|
29
30
|
* A caller that must turn work away rather than queue it reads `waitingCount`
|
|
30
31
|
* before calling `acquire`. Nothing awaits between that read and the enqueue
|
|
@@ -1127,9 +1127,6 @@ export class AiSdkTurn {
|
|
|
1127
1127
|
}
|
|
1128
1128
|
const requestSystemPromptBase = joinPromptFragments([
|
|
1129
1129
|
systemPrompt,
|
|
1130
|
-
priorUnknownProjection.kind === 'projected'
|
|
1131
|
-
? priorUnknownProjection.systemNotice
|
|
1132
|
-
: undefined,
|
|
1133
1130
|
finalChildSummaryStep ? CHILD_STEP_BUDGET_FINALIZATION_PROMPT : undefined,
|
|
1134
1131
|
toolRuntime.hasSandboxBoundaryDenial() ? SANDBOX_BOUNDARY_DENIED_FOR_TURN : undefined,
|
|
1135
1132
|
sandboxBoundaryFinalizationStep ? SANDBOX_BOUNDARY_FINALIZATION_PROMPT : undefined,
|
|
@@ -177,20 +177,10 @@ export function inspectPriorUnknownToolOutcomes(events, invocations) {
|
|
|
177
177
|
}
|
|
178
178
|
if (outcomes.length === 0)
|
|
179
179
|
return { kind: 'none' };
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
rows.push(`- and ${outcomes.length - 64} more unknown operations`);
|
|
185
|
-
return {
|
|
186
|
-
kind: 'projected',
|
|
187
|
-
outcomes,
|
|
188
|
-
systemNotice: [
|
|
189
|
-
'A prior execution was interrupted after these tools were dispatched. No durable tool results were recorded; their side effects may or may not have happened.',
|
|
190
|
-
'The outcome_unknown tool responses in history are temporary context, not proof of success or failure. Inspect current state before repeating any action.',
|
|
191
|
-
...rows,
|
|
192
|
-
].join('\n'),
|
|
193
|
-
};
|
|
180
|
+
// The unknown is carried only by the request-local tool responses below
|
|
181
|
+
// (appendPriorUnknownToolResponses): the system prompt stays byte-stable,
|
|
182
|
+
// so the provider's cached request prefix is never churned per event.
|
|
183
|
+
return { kind: 'projected', outcomes };
|
|
194
184
|
}
|
|
195
185
|
/** Insert request-only tool responses beside their calls so provider history stays well-formed. */
|
|
196
186
|
export function appendPriorUnknownToolResponses(events, projection) {
|
|
@@ -131,6 +131,13 @@ export class PluginStorageService extends PluginNamespacedDataService {
|
|
|
131
131
|
const result = await this.mutateValues([{ key, value, ...options }]);
|
|
132
132
|
return result[key];
|
|
133
133
|
}
|
|
134
|
+
/** The callback must drain any process using the directory before settling. */
|
|
135
|
+
withScratchDirectory(key, signal, use) {
|
|
136
|
+
const runtime = this.runtime();
|
|
137
|
+
if (!runtime.withScratchDirectory)
|
|
138
|
+
throw new Error('Plugin scratch directories are unavailable');
|
|
139
|
+
return runtime.withScratchDirectory(this.namespace(), validKey(key), signal, use);
|
|
140
|
+
}
|
|
134
141
|
async delete(key, options = {}) {
|
|
135
142
|
const result = await this.mutateValues([{ key, ...options }]);
|
|
136
143
|
return result[key];
|
|
@@ -66,6 +66,9 @@ export class PluginExecutorService extends Service {
|
|
|
66
66
|
? { configureConversation: provider.configureConversation.bind(provider) }
|
|
67
67
|
: {}),
|
|
68
68
|
...(provider.discover ? { discover: provider.discover.bind(provider) } : {}),
|
|
69
|
+
...(provider.invalidateCatalog
|
|
70
|
+
? { invalidateCatalog: provider.invalidateCatalog.bind(provider) }
|
|
71
|
+
: {}),
|
|
69
72
|
...(provider.inspectConversation
|
|
70
73
|
? { inspectConversation: provider.inspectConversation.bind(provider) }
|
|
71
74
|
: {}),
|
|
@@ -152,7 +155,11 @@ export class PluginExecutorService extends Service {
|
|
|
152
155
|
cwd: input.cwd,
|
|
153
156
|
...(input.configuration ? { configuration: input.configuration } : {}),
|
|
154
157
|
})
|
|
155
|
-
: await entry.provider.discover?.({
|
|
158
|
+
: await entry.provider.discover?.({
|
|
159
|
+
cwd: input.cwd,
|
|
160
|
+
signal: combined,
|
|
161
|
+
...(input.refresh ? { refresh: true } : {}),
|
|
162
|
+
});
|
|
156
163
|
combined.throwIfAborted();
|
|
157
164
|
if (!result)
|
|
158
165
|
return fallback;
|
|
@@ -163,6 +170,12 @@ export class PluginExecutorService extends Service {
|
|
|
163
170
|
}
|
|
164
171
|
})));
|
|
165
172
|
}
|
|
173
|
+
invalidateCatalog() {
|
|
174
|
+
for (const entry of this.registry.entries('profile')) {
|
|
175
|
+
if (!entry.retired)
|
|
176
|
+
entry.provider.invalidateCatalog?.();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
166
179
|
async configureConversation(sessionId, executorId, input) {
|
|
167
180
|
const entry = this.entry(sessionId, executorId);
|
|
168
181
|
const configure = entry.provider.configureConversation;
|
|
@@ -171,8 +184,11 @@ export class PluginExecutorService extends Service {
|
|
|
171
184
|
throw new Error('Executor configuration is unavailable or busy');
|
|
172
185
|
if (!isExecutorConfiguration(input.configuration))
|
|
173
186
|
throw new TypeError('Invalid executor configuration');
|
|
174
|
-
await this.withActiveOperation(entry, { conversationKey: input.conversationKey }, async (signal) => {
|
|
175
|
-
await configure(input, AbortSignal.any([signal, AbortSignal.timeout(30_000)]));
|
|
187
|
+
return await this.withActiveOperation(entry, { conversationKey: input.conversationKey }, async (signal) => {
|
|
188
|
+
const confirmed = await configure(input, AbortSignal.any([signal, AbortSignal.timeout(30_000)]));
|
|
189
|
+
if (confirmed !== undefined && !isExecutorConfiguration(confirmed))
|
|
190
|
+
throw new TypeError('Invalid confirmed executor configuration');
|
|
191
|
+
return confirmed;
|
|
176
192
|
});
|
|
177
193
|
}
|
|
178
194
|
/** Task retirement releases retained provider resources; backend refresh must not. */
|
|
@@ -42,9 +42,23 @@ export class RuntimeReadModel {
|
|
|
42
42
|
async getSessionView(sessionId) {
|
|
43
43
|
const diagnostics = [];
|
|
44
44
|
const inFlightTurnIds = new Set();
|
|
45
|
+
let snapshot;
|
|
46
|
+
if (this.deps.runtimeEventStore.readSessionRuntimeSnapshot) {
|
|
47
|
+
try {
|
|
48
|
+
snapshot = await this.deps.runtimeEventStore.readSessionRuntimeSnapshot(sessionId);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
throw new RuntimeReadModelError('RuntimeEvent Session snapshot read failed', [
|
|
52
|
+
readModelDiagnostic('unsupported_event', 'RuntimeEventStore.readSessionRuntimeSnapshot failed', {
|
|
53
|
+
error: errorMessage(error),
|
|
54
|
+
}),
|
|
55
|
+
]);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
45
58
|
let invocations;
|
|
46
59
|
try {
|
|
47
|
-
invocations = (
|
|
60
|
+
invocations = (snapshot?.invocations ??
|
|
61
|
+
(await this.deps.runtimeEventStore.listSessionInvocations(sessionId))).filter((invocation) => isSessionInlineInvocation(invocation.opening));
|
|
48
62
|
}
|
|
49
63
|
catch (error) {
|
|
50
64
|
throw new RuntimeReadModelError('RuntimeReadModel could not list Session invocations', [
|
|
@@ -56,15 +70,20 @@ export class RuntimeReadModel {
|
|
|
56
70
|
if (invocations.length === 0) {
|
|
57
71
|
return this.buildView({ invocations, events: [], diagnostics });
|
|
58
72
|
}
|
|
59
|
-
const
|
|
60
|
-
|
|
73
|
+
const durableEventOrdinalById = snapshot?.durableEventOrdinalById ??
|
|
74
|
+
new Map((await this.readSessionRuntimeEventOrdinals(sessionId)).map(({ event, ordinal }) => [
|
|
75
|
+
event.id,
|
|
76
|
+
ordinal,
|
|
77
|
+
]));
|
|
61
78
|
const ordered = [];
|
|
62
79
|
const terminalFacts = [];
|
|
63
80
|
for (let runIndex = 0; runIndex < invocations.length; runIndex += 1) {
|
|
64
81
|
const invocation = invocations[runIndex];
|
|
65
82
|
let runEvents;
|
|
66
83
|
try {
|
|
67
|
-
runEvents =
|
|
84
|
+
runEvents = snapshot
|
|
85
|
+
? (snapshot.eventsByRun.get(invocation.runId) ?? [])
|
|
86
|
+
: await this.deps.runtimeEventStore.readRuntimeEvents(sessionId, invocation.runId);
|
|
68
87
|
}
|
|
69
88
|
catch (error) {
|
|
70
89
|
throw new RuntimeReadModelError('RuntimeEvent ledger read failed', [
|
|
@@ -3599,6 +3599,7 @@ function sessionConfigurationMatchesExceptPermissionMode(header, configuration)
|
|
|
3599
3599
|
return (header.backend === configuration.backend &&
|
|
3600
3600
|
header.executorId === configuration.executorId &&
|
|
3601
3601
|
header.executorConfig?.model === configuration.executorConfig?.model &&
|
|
3602
|
+
header.executorConfig?.mode === configuration.executorConfig?.mode &&
|
|
3602
3603
|
header.llmConnectionId === configuration.llmConnectionId &&
|
|
3603
3604
|
header.llmConnectionSlug === configuration.llmConnectionSlug &&
|
|
3604
3605
|
header.connectionLocked === configuration.connectionLocked &&
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
8
|
+
"./admission-limiter": "./dist/admission-limiter.js",
|
|
8
9
|
"./ask-user-question-tool": "./dist/ask-user-question-tool.js",
|
|
9
10
|
"./ai-sdk-backend": "./dist/ai-sdk-backend.js",
|
|
10
11
|
"./mcp-tools": "./dist/mcp-tools.js",
|
|
@@ -0,0 +1,49 @@
|
|
|
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 { constants } from 'node:fs';
|
|
20
|
+
import { access, realpath, stat } from 'node:fs/promises';
|
|
21
|
+
import { dirname, isAbsolute, join } from 'node:path';
|
|
22
|
+
/** Public diagnostics survive Electron IPC without exposing filesystem error details. */
|
|
23
|
+
export class AntigravityProgramError extends Error {
|
|
24
|
+
failure;
|
|
25
|
+
constructor(failure) {
|
|
26
|
+
super(`Antigravity program: ${failure}`);
|
|
27
|
+
this.failure = failure;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Shared by native program selection and the Host's check/sign-in admission. */
|
|
31
|
+
export async function checkAntigravityProgram(executable) {
|
|
32
|
+
const resolved = await checkedFile(executable, 'executable_unavailable');
|
|
33
|
+
const helper = await checkedFile(join(dirname(resolved), 'localharness_external'), 'helper_unavailable');
|
|
34
|
+
return { executable: resolved, helper };
|
|
35
|
+
}
|
|
36
|
+
async function checkedFile(path, failure) {
|
|
37
|
+
try {
|
|
38
|
+
if (!isAbsolute(path))
|
|
39
|
+
throw new Error('Absolute path required');
|
|
40
|
+
const resolved = await realpath(path);
|
|
41
|
+
if (!(await stat(resolved)).isFile())
|
|
42
|
+
throw new Error('File required');
|
|
43
|
+
await access(resolved, constants.X_OK);
|
|
44
|
+
return resolved;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
throw new AntigravityProgramError(failure);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -65,7 +65,9 @@ export const RUNTIME_HOST_REGISTRATION_SCHEMA_VERSION = 1;
|
|
|
65
65
|
export const RUNTIME_HOST_PROTOCOL_VERSION = 0;
|
|
66
66
|
// Increment when the same protocol version no longer guarantees safe Client-Host
|
|
67
67
|
// interoperability. Mismatches are rejected before domain commands are admitted.
|
|
68
|
-
export const RUNTIME_HOST_COMPATIBILITY_EPOCH =
|
|
68
|
+
export const RUNTIME_HOST_COMPATIBILITY_EPOCH = 204;
|
|
69
|
+
// 204: Executor catalogs and Session configuration carry opaque mode IDs;
|
|
70
|
+
// catalog queries may request a provider refresh. Older peers reject these fields.
|
|
69
71
|
// 202: `session.remove.preview` takes a bounded list of Sessions and reports the
|
|
70
72
|
// child tasks, worktrees and optionally the bytes their removal would delete;
|
|
71
73
|
// `session.remove` takes `requireArchivedForMs` and may answer `too_recent`.
|
|
@@ -93,8 +93,14 @@ export const PLUGIN_PLATFORM_OPERATION_SPECS = {
|
|
|
93
93
|
decodeInput: (value) => {
|
|
94
94
|
const record = requireRecord(value, 'Executor query');
|
|
95
95
|
if (record.kind === 'catalog') {
|
|
96
|
-
const input =
|
|
97
|
-
|
|
96
|
+
const input = requireShapedRecord(record, 'Executor catalog', ['kind', 'cwd'], ['refresh']);
|
|
97
|
+
if (input.refresh !== undefined && typeof input.refresh !== 'boolean')
|
|
98
|
+
throw invalidProtocolFrame('Invalid executor refresh flag');
|
|
99
|
+
return {
|
|
100
|
+
kind: 'catalog',
|
|
101
|
+
cwd: requireString(input.cwd, 'cwd', 4096),
|
|
102
|
+
...(input.refresh ? { refresh: true } : {}),
|
|
103
|
+
};
|
|
98
104
|
}
|
|
99
105
|
const input = requireExactRecord(record, 'Executor conversation', ['kind', 'sessionId']);
|
|
100
106
|
if (input.kind !== 'conversation')
|
|
@@ -16,12 +16,11 @@
|
|
|
16
16
|
* specific language governing permissions and limitations
|
|
17
17
|
* under the License.
|
|
18
18
|
*/
|
|
19
|
-
import {
|
|
20
|
-
import { access, realpath, stat } from 'node:fs/promises';
|
|
21
|
-
import { dirname, join, isAbsolute } from 'node:path';
|
|
19
|
+
import { dirname } from 'node:path';
|
|
22
20
|
import { StringDecoder } from 'node:string_decoder';
|
|
23
21
|
import { methods, RequestError } from '@agentclientprotocol/sdk';
|
|
24
22
|
import { AcpSetupError, withAcpConnection } from './connection.js';
|
|
23
|
+
import { checkAntigravityProgram, AntigravityProgramError } from '../../antigravity-program.js';
|
|
25
24
|
const AUTH_PREFIX = 'Open the following link to authenticate the ACP server: ';
|
|
26
25
|
const INITIALIZE_TIMEOUT_MS = 30_000;
|
|
27
26
|
const LOGIN_TIMEOUT_MS = 5 * 60_000;
|
|
@@ -45,8 +44,7 @@ export async function runAntigravitySetup(input) {
|
|
|
45
44
|
});
|
|
46
45
|
try {
|
|
47
46
|
signal.throwIfAborted();
|
|
48
|
-
const executable = await
|
|
49
|
-
const helper = await checkedFile(join(dirname(executable), 'localharness_external'), 'helper_unavailable');
|
|
47
|
+
const { executable, helper } = await checkAntigravityProgram(input.executable);
|
|
50
48
|
await withAcpConnection({
|
|
51
49
|
executable,
|
|
52
50
|
cwd: dirname(executable),
|
|
@@ -96,6 +94,8 @@ export async function runAntigravitySetup(input) {
|
|
|
96
94
|
});
|
|
97
95
|
}
|
|
98
96
|
catch (error) {
|
|
97
|
+
if (error instanceof AntigravityProgramError)
|
|
98
|
+
throw new AcpSetupError(error.failure);
|
|
99
99
|
if (error instanceof AcpSetupError)
|
|
100
100
|
throw error;
|
|
101
101
|
signal.throwIfAborted();
|
|
@@ -105,20 +105,6 @@ export async function runAntigravitySetup(input) {
|
|
|
105
105
|
clearTimeout(timeout);
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
-
async function checkedFile(path, failure) {
|
|
109
|
-
try {
|
|
110
|
-
if (!isAbsolute(path))
|
|
111
|
-
throw new Error('Absolute path required');
|
|
112
|
-
const resolved = await realpath(path);
|
|
113
|
-
if (!(await stat(resolved)).isFile())
|
|
114
|
-
throw new Error('File required');
|
|
115
|
-
await access(resolved, constants.X_OK);
|
|
116
|
-
return resolved;
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
throw new AcpSetupError(failure);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
108
|
/** Official 1.1.1 prints its login link to stderr; ordinary diagnostics are discarded. */
|
|
123
109
|
export function createAntigravityStderrHandler(onAuthorizationUrl) {
|
|
124
110
|
const recognize = (line) => {
|
|
@@ -274,7 +274,7 @@ export async function createExecutionRuntimeHostComposition(context, options = {
|
|
|
274
274
|
...(header.executorConfig ? { configuration: header.executorConfig } : {}),
|
|
275
275
|
});
|
|
276
276
|
}
|
|
277
|
-
const catalog = await pluginExecutors.catalog({ cwd: input.cwd });
|
|
277
|
+
const catalog = await pluginExecutors.catalog({ cwd: input.cwd, refresh: input.refresh });
|
|
278
278
|
return withBuiltinExternalAgentCatalog(catalog);
|
|
279
279
|
});
|
|
280
280
|
const openedProjectCatalog = storage.projectCatalog;
|
|
@@ -1333,6 +1333,10 @@ export async function createExecutionRuntimeHostComposition(context, options = {
|
|
|
1333
1333
|
context.retainUntilProcessExit();
|
|
1334
1334
|
context.requestDrain();
|
|
1335
1335
|
},
|
|
1336
|
+
onSucceeded: () => {
|
|
1337
|
+
pluginExecutors.invalidateCatalog();
|
|
1338
|
+
hostChanges.publishConfiguration();
|
|
1339
|
+
},
|
|
1336
1340
|
capabilities: clientCapabilities,
|
|
1337
1341
|
});
|
|
1338
1342
|
oauth = new HostOAuthCoordinator({
|
|
@@ -1759,6 +1763,7 @@ export async function createExecutionRuntimeHostComposition(context, options = {
|
|
|
1759
1763
|
});
|
|
1760
1764
|
async function applyRuntimePolicyMutationEffects() {
|
|
1761
1765
|
try {
|
|
1766
|
+
pluginExecutors.invalidateCatalog();
|
|
1762
1767
|
await builtinExternalAgentPlugins.reconcile();
|
|
1763
1768
|
await requireMemory(memory).refreshAfterPolicyMutation();
|
|
1764
1769
|
}
|
|
@@ -1783,10 +1788,11 @@ export async function createExecutionRuntimeHostComposition(context, options = {
|
|
|
1783
1788
|
continuity: continuityCoordinator,
|
|
1784
1789
|
workspaceResolver,
|
|
1785
1790
|
requestDrain: context.requestDrain,
|
|
1791
|
+
isTurnBusy: (sessionId) => rootCoordinator?.hasActiveOrPendingTurn(sessionId) ?? false,
|
|
1786
1792
|
configureExecutor: async (header, configuration) => {
|
|
1787
1793
|
if (!header.executorId)
|
|
1788
1794
|
throw new Error('Session has no executor');
|
|
1789
|
-
await pluginExecutors.configureConversation(header.id, header.executorId, {
|
|
1795
|
+
return await pluginExecutors.configureConversation(header.id, header.executorId, {
|
|
1790
1796
|
conversationKey: header.id,
|
|
1791
1797
|
cwd: header.cwd,
|
|
1792
1798
|
configuration,
|
|
@@ -1804,10 +1810,15 @@ export async function createExecutionRuntimeHostComposition(context, options = {
|
|
|
1804
1810
|
throw new Error('Executor is not ready');
|
|
1805
1811
|
// Catalog-managed executors pin their confirmed configuration on every create path.
|
|
1806
1812
|
// Providers without model discovery retain main's executor-specific model contract.
|
|
1807
|
-
if (entry.supportsModelChange ||
|
|
1813
|
+
if (entry.supportsModelChange ||
|
|
1814
|
+
entry.models.length > 0 ||
|
|
1815
|
+
entry.supportsModeChange ||
|
|
1816
|
+
entry.modes?.length) {
|
|
1808
1817
|
if (configuration?.model &&
|
|
1809
1818
|
!entry.models.some((model) => model.id === configuration.model))
|
|
1810
1819
|
throw new Error('Executor model is unavailable');
|
|
1820
|
+
if (configuration?.mode && !entry.modes?.some((mode) => mode.id === configuration.mode))
|
|
1821
|
+
throw new Error('Executor mode is unavailable');
|
|
1811
1822
|
return configuration ?? {};
|
|
1812
1823
|
}
|
|
1813
1824
|
},
|
|
@@ -132,6 +132,7 @@ export class HostExternalAgentSetupCoordinator {
|
|
|
132
132
|
});
|
|
133
133
|
attempt.abort.signal.throwIfAborted();
|
|
134
134
|
attempt.projection = { ...attempt.projection, phase: 'succeeded', installedExecutable };
|
|
135
|
+
this.deps.onSucceeded?.();
|
|
135
136
|
return;
|
|
136
137
|
}
|
|
137
138
|
await (this.deps.run ?? runAntigravitySetup)({
|
|
@@ -157,6 +158,8 @@ export class HostExternalAgentSetupCoordinator {
|
|
|
157
158
|
}
|
|
158
159
|
},
|
|
159
160
|
});
|
|
161
|
+
if (!attempt.abort.signal.aborted)
|
|
162
|
+
this.deps.onSucceeded?.();
|
|
160
163
|
attempt.projection = {
|
|
161
164
|
...attempt.projection,
|
|
162
165
|
phase: attempt.abort.signal.aborted ? 'cancelled' : 'succeeded',
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'node:crypto';
|
|
20
20
|
import { chmod, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
21
21
|
import { dirname, join } from 'node:path';
|
|
22
|
+
import { withPluginScratchDirectory } from './plugin-scratch-directory.js';
|
|
22
23
|
const NAMESPACE_QUOTA_BYTES = 10 * 1024 * 1024;
|
|
23
24
|
/** Durable per-extension data authority. JSON writes are atomic; secrets are AES-GCM sealed at rest. */
|
|
24
25
|
export class HostPluginDataRuntime {
|
|
@@ -29,6 +30,11 @@ export class HostPluginDataRuntime {
|
|
|
29
30
|
constructor(controlDirectory) {
|
|
30
31
|
this.#root = join(controlDirectory, 'plugin-data');
|
|
31
32
|
}
|
|
33
|
+
withScratchDirectory(namespace, key, signal, use) {
|
|
34
|
+
validateNamespace(namespace);
|
|
35
|
+
const identity = createHash('sha256').update(key).digest('hex');
|
|
36
|
+
return withPluginScratchDirectory(join(this.#root, digest(namespace), 'scratch', identity), signal, use);
|
|
37
|
+
}
|
|
32
38
|
async read(namespace, domain, key) {
|
|
33
39
|
return await this.#withNamespace(namespace, async () => {
|
|
34
40
|
const item = (await this.#readData(namespace, domain)).values[key];
|
|
@@ -0,0 +1,55 @@
|
|
|
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 { lstat, mkdir, realpath, rm } from 'node:fs/promises';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
22
|
+
import { tryAcquireFileLifetimeOwner } from '@maka/storage/file-lifetime-owner';
|
|
23
|
+
/** Native leases cover separate runtime instances and are released by the OS on exit. */
|
|
24
|
+
export async function withPluginScratchDirectory(root, signal, use) {
|
|
25
|
+
signal.throwIfAborted();
|
|
26
|
+
const lease = await acquire(root, signal);
|
|
27
|
+
const directory = join(root, 'workspace');
|
|
28
|
+
try {
|
|
29
|
+
signal.throwIfAborted();
|
|
30
|
+
await rm(directory, { recursive: true, force: true });
|
|
31
|
+
await mkdir(directory, { mode: 0o700 });
|
|
32
|
+
const state = await lstat(directory);
|
|
33
|
+
if (!state.isDirectory() || state.isSymbolicLink())
|
|
34
|
+
throw new Error('Plugin scratch cwd is not a directory');
|
|
35
|
+
signal.throwIfAborted();
|
|
36
|
+
return await use(await realpath(directory));
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
try {
|
|
40
|
+
await rm(directory, { recursive: true, force: true });
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
await lease.close();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function acquire(root, signal) {
|
|
48
|
+
while (true) {
|
|
49
|
+
signal.throwIfAborted();
|
|
50
|
+
const lease = await tryAcquireFileLifetimeOwner(join(root, 'owner.lease'));
|
|
51
|
+
if (lease)
|
|
52
|
+
return lease;
|
|
53
|
+
await delay(25, undefined, { signal });
|
|
54
|
+
}
|
|
55
|
+
}
|