maka-agent 0.2.0-dev.51.20260925 → 0.2.0-dev.52.20260926
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acp/goal-plan-operations.js +142 -0
- package/dist/acp/goal-plan-routes.js +33 -0
- package/dist/acp/maka-acp-agent.js +41 -1
- package/dist/acp/session-domain-observation.js +185 -0
- package/dist/acp/session-event-mapper.js +1 -1
- package/dist/acp/session-registry.js +417 -9
- package/dist/acp/tool-event-mapper.js +36 -3
- package/dist/runtime-host-session-channel.js +4 -0
- package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
- package/node_modules/@maka/core/dist/openai-urls.js +51 -0
- package/node_modules/@maka/core/dist/relative-time.js +1 -9
- package/node_modules/@maka/core/package.json +1 -0
- package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +10 -8
- package/node_modules/@maka/runtime/dist/builtin-tools.js +2 -2
- package/node_modules/@maka/runtime/dist/filesystem-executor.js +3 -2
- package/node_modules/@maka/runtime/dist/filesystem-worker/protocol.js +7 -4
- package/node_modules/@maka/runtime/dist/glob-search.js +18 -5
- package/node_modules/@maka/runtime/dist/model-factory.js +27 -1
- package/node_modules/@maka/runtime/dist/model-fetcher.js +2 -1
- package/node_modules/@maka/runtime/dist/model-runtime.js +5 -3
- package/node_modules/@maka/runtime/dist/provider-urls.js +1 -18
- package/node_modules/@maka/runtime/dist/scheduled-task-tools.js +27 -2
- package/node_modules/@maka/runtime/dist/test-connection.js +2 -2
- package/node_modules/@maka/runtime/dist/workers/filesystem-worker.js +16 -6
- package/node_modules/@maka/runtime-host/dist/protocol/index.js +3 -1
- package/node_modules/@maka/runtime-host/dist/protocol/scheduled-task.js +8 -1
- package/node_modules/@maka/runtime-host/dist/protocol/session-catalog-change.js +24 -6
- package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +16 -3
- package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +20 -16
- package/node_modules/@maka/runtime-host/dist/server/execution-model-composition.js +18 -1
- package/node_modules/@maka/runtime-host/dist/server/host-change-feed.js +15 -22
- package/node_modules/@maka/runtime-host/dist/server/interaction-coordinator.js +30 -3
- package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +1 -1
- package/node_modules/@maka/runtime-host/dist/server/session-continuity-coordinator.js +16 -3
- package/package.json +1 -1
|
@@ -0,0 +1,142 @@
|
|
|
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 { RequestError } from '@agentclientprotocol/sdk';
|
|
20
|
+
import { RuntimeHostOperationError, RuntimeHostRequestInterruptedError, } from '@maka/runtime-host/client';
|
|
21
|
+
export class AcpGoalPlanOperations {
|
|
22
|
+
#port;
|
|
23
|
+
constructor(port) {
|
|
24
|
+
this.#port = port;
|
|
25
|
+
}
|
|
26
|
+
goalQuery(input, context) {
|
|
27
|
+
return this.#run('goal.query', input, context, !!context.notifyGoalStatus);
|
|
28
|
+
}
|
|
29
|
+
goalArm(input, context) {
|
|
30
|
+
return this.#run('goal.arm', input, context, true);
|
|
31
|
+
}
|
|
32
|
+
goalControl(input, context) {
|
|
33
|
+
return this.#run('goal.control', input, context, true);
|
|
34
|
+
}
|
|
35
|
+
planQuery(input, context) {
|
|
36
|
+
return this.#run('plan.query', input, context, !!context.notifyPlanChanged);
|
|
37
|
+
}
|
|
38
|
+
planControl(input, context) {
|
|
39
|
+
return this.#run('plan.control', input, context, true);
|
|
40
|
+
}
|
|
41
|
+
async planTurnStart(input, context) {
|
|
42
|
+
const prepared = await this.#port.prepare(input.sessionId, context, input.turnId, true);
|
|
43
|
+
let dispatched = false;
|
|
44
|
+
const onAbort = () => prepared.cancelObservation?.();
|
|
45
|
+
context.signal.addEventListener('abort', onAbort, { once: true });
|
|
46
|
+
if (context.signal.aborted)
|
|
47
|
+
onAbort();
|
|
48
|
+
try {
|
|
49
|
+
context.signal.throwIfAborted();
|
|
50
|
+
this.#port.assertCurrent(input.sessionId);
|
|
51
|
+
const observation = prepared.observation;
|
|
52
|
+
if (!observation && !prepared.observedReplay)
|
|
53
|
+
throw new Error('Plan Turn observer was not installed');
|
|
54
|
+
observation?.markDispatched();
|
|
55
|
+
dispatched = true;
|
|
56
|
+
const result = await prepared.connection.request('plan.turn.start', input);
|
|
57
|
+
observation?.settleStartRequest(result.turn);
|
|
58
|
+
prepared.commit();
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (dispatched && isDispatchedUnknown(error)) {
|
|
63
|
+
prepared.observation?.failStartRequest(error);
|
|
64
|
+
prepared.reconcileAdmission?.();
|
|
65
|
+
prepared.commit();
|
|
66
|
+
throw RequestError.internalError({
|
|
67
|
+
source: 'runtime_host',
|
|
68
|
+
operation: 'plan.turn.start',
|
|
69
|
+
code: 'outcome_unknown',
|
|
70
|
+
dispatch: 'dispatched',
|
|
71
|
+
sessionId: input.sessionId,
|
|
72
|
+
turnId: input.turnId,
|
|
73
|
+
...(input.kind === 'approve_proposal'
|
|
74
|
+
? { proposalId: input.proposalId }
|
|
75
|
+
: { executionId: input.executionId }),
|
|
76
|
+
}, 'Plan Turn start outcome is unknown; query the Plan and exact Turn before retrying');
|
|
77
|
+
}
|
|
78
|
+
if (dispatched && isPersistenceUnknown(error)) {
|
|
79
|
+
prepared.observation?.failStartRequest(error);
|
|
80
|
+
prepared.reconcileAdmission?.();
|
|
81
|
+
prepared.commit();
|
|
82
|
+
throw this.#port.mapError(error, 'plan.turn.start', {
|
|
83
|
+
sessionId: input.sessionId,
|
|
84
|
+
turnId: input.turnId,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
prepared.rollback(error);
|
|
88
|
+
if (error instanceof RequestError)
|
|
89
|
+
throw error;
|
|
90
|
+
throw this.#port.mapError(error, 'plan.turn.start', {
|
|
91
|
+
sessionId: input.sessionId,
|
|
92
|
+
turnId: input.turnId,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
context.signal.removeEventListener('abort', onAbort);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async #run(operation, input, context, observe) {
|
|
100
|
+
const prepared = await this.#port.prepare(input.sessionId, context, undefined, observe);
|
|
101
|
+
let dispatched = false;
|
|
102
|
+
try {
|
|
103
|
+
context.signal.throwIfAborted();
|
|
104
|
+
this.#port.assertCurrent(input.sessionId);
|
|
105
|
+
dispatched = true;
|
|
106
|
+
// The Host protocol validates both request and response against the operation spec.
|
|
107
|
+
const result = await prepared.connection.request(operation, input);
|
|
108
|
+
prepared.commit();
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (dispatched && isDispatchedUnknown(error)) {
|
|
113
|
+
prepared.commit();
|
|
114
|
+
throw RequestError.internalError({
|
|
115
|
+
source: 'runtime_host',
|
|
116
|
+
operation,
|
|
117
|
+
code: 'outcome_unknown',
|
|
118
|
+
dispatch: 'dispatched',
|
|
119
|
+
sessionId: input.sessionId,
|
|
120
|
+
...('goalId' in input ? { goalId: input.goalId } : {}),
|
|
121
|
+
...('operationId' in input ? { operationId: input.operationId } : {}),
|
|
122
|
+
...('proposalId' in input ? { proposalId: input.proposalId } : {}),
|
|
123
|
+
...('executionId' in input ? { executionId: input.executionId } : {}),
|
|
124
|
+
}, 'Runtime Host operation outcome is unknown; query authoritative state before retrying');
|
|
125
|
+
}
|
|
126
|
+
if (dispatched && isPersistenceUnknown(error)) {
|
|
127
|
+
prepared.commit();
|
|
128
|
+
throw this.#port.mapError(error, operation, { sessionId: input.sessionId });
|
|
129
|
+
}
|
|
130
|
+
prepared.rollback(error);
|
|
131
|
+
if (error instanceof RequestError)
|
|
132
|
+
throw error;
|
|
133
|
+
throw this.#port.mapError(error, operation, { sessionId: input.sessionId });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function isDispatchedUnknown(error) {
|
|
138
|
+
return error instanceof RuntimeHostRequestInterruptedError && error.dispatch === 'dispatched';
|
|
139
|
+
}
|
|
140
|
+
function isPersistenceUnknown(error) {
|
|
141
|
+
return error instanceof RuntimeHostOperationError && error.code === 'persistence_failed';
|
|
142
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
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 { RequestError } from '@agentclientprotocol/sdk';
|
|
20
|
+
import { HOST_OPERATION_SPECS } from '@maka/runtime-host/protocol';
|
|
21
|
+
/** Keep ACP extension parsing identical to the Host's current wire contract. */
|
|
22
|
+
export function goalPlanRouteInput(operation) {
|
|
23
|
+
return {
|
|
24
|
+
parse(value) {
|
|
25
|
+
try {
|
|
26
|
+
return HOST_OPERATION_SPECS[operation].decodeInput(value);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
throw RequestError.invalidParams({ reason: 'invalid_goal_plan_input', operation }, `Invalid ${operation} request`);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { agent, methods, RequestError, } from '@agentclientprotocol/sdk';
|
|
20
20
|
import { HOST_OPERATION_SPECS } from '@maka/runtime-host/protocol';
|
|
21
|
+
import { goalPlanRouteInput } from './goal-plan-routes.js';
|
|
21
22
|
export function createMakaAcpAgent(options) {
|
|
22
23
|
let clientCapabilities = {};
|
|
23
24
|
return agent({ name: 'maka' })
|
|
@@ -28,6 +29,7 @@ export function createMakaAcpAgent(options) {
|
|
|
28
29
|
agentCapabilities: {
|
|
29
30
|
loadSession: true,
|
|
30
31
|
sessionCapabilities: { list: {}, resume: {}, close: {} },
|
|
32
|
+
_meta: { '_maka/goalPlan': { version: 1 } },
|
|
31
33
|
},
|
|
32
34
|
authMethods: [],
|
|
33
35
|
agentInfo: { name: 'maka', title: 'Maka', version: options.version },
|
|
@@ -44,6 +46,12 @@ export function createMakaAcpAgent(options) {
|
|
|
44
46
|
throw RequestError.invalidParams({ reason: 'invalid_resume_query' }, 'Invalid Turn resume request');
|
|
45
47
|
}
|
|
46
48
|
}, ({ params, signal, client }) => options.sessionRegistry.resumeTurn(params, sessionContext(client, signal, clientCapabilities, true)))
|
|
49
|
+
.onRequest('_maka/goal/query', goalPlanRouteInput('goal.query'), ({ params, signal, client }) => options.sessionRegistry.goalQuery(params, sessionContext(client, signal, clientCapabilities, true)))
|
|
50
|
+
.onRequest('_maka/goal/arm', goalPlanRouteInput('goal.arm'), ({ params, signal, client }) => options.sessionRegistry.goalArm(params, sessionContext(client, signal, clientCapabilities, true)))
|
|
51
|
+
.onRequest('_maka/goal/control', goalPlanRouteInput('goal.control'), ({ params, signal, client }) => options.sessionRegistry.goalControl(params, sessionContext(client, signal, clientCapabilities, true)))
|
|
52
|
+
.onRequest('_maka/plan/query', goalPlanRouteInput('plan.query'), ({ params, signal, client }) => options.sessionRegistry.planQuery(params, sessionContext(client, signal, clientCapabilities, true)))
|
|
53
|
+
.onRequest('_maka/plan/control', goalPlanRouteInput('plan.control'), ({ params, signal, client }) => options.sessionRegistry.planControl(params, sessionContext(client, signal, clientCapabilities, true)))
|
|
54
|
+
.onRequest('_maka/plan/turn/start', goalPlanRouteInput('plan.turn.start'), ({ params, signal, client }) => options.sessionRegistry.planTurnStart(params, sessionContext(client, signal, clientCapabilities, true)))
|
|
47
55
|
.onRequest('_maka/session/copy-source/query', {
|
|
48
56
|
parse: (value) => {
|
|
49
57
|
try {
|
|
@@ -88,7 +96,33 @@ export function createMakaAcpAgent(options) {
|
|
|
88
96
|
.onRequest(methods.agent.session.setConfigOption, ({ params }) => options.sessionRegistry.setConfigOption(params))
|
|
89
97
|
.onRequest(methods.agent.session.prompt, ({ params, signal, client }) => options.sessionRegistry.prompt(params, sessionContext(client, signal, clientCapabilities)))
|
|
90
98
|
.onNotification(methods.agent.session.cancel, ({ params }) => options.sessionRegistry.cancel(params))
|
|
91
|
-
.onRequest(methods.agent.session.close, ({ params }) => options.sessionRegistry.close(params))
|
|
99
|
+
.onRequest(methods.agent.session.close, ({ params }) => options.sessionRegistry.close(params))
|
|
100
|
+
.onRequest('_maka/artifact/query', extensionParams('artifact.query', HOST_OPERATION_SPECS['artifact.query'].decodeInput), ({ params }) => options.sessionRegistry.artifactQuery(params))
|
|
101
|
+
.onRequest('_maka/artifact/ingest', extensionParams('artifact.ingest', HOST_OPERATION_SPECS['artifact.ingest'].decodeInput), ({ params }) => options.sessionRegistry.artifactIngest(params))
|
|
102
|
+
.onRequest('_maka/artifact/delete', extensionParams('artifact.delete', HOST_OPERATION_SPECS['artifact.delete'].decodeInput), ({ params }) => options.sessionRegistry.artifactDelete(params))
|
|
103
|
+
.onRequest('_maka/memory/query', extensionParams('memory.query', HOST_OPERATION_SPECS['memory.query'].decodeInput), ({ params }) => options.sessionRegistry.memoryQuery(params))
|
|
104
|
+
.onRequest('_maka/memory/mutate', extensionParams('memory.mutate', HOST_OPERATION_SPECS['memory.mutate'].decodeInput), ({ params }) => options.sessionRegistry.memoryMutate(params));
|
|
105
|
+
}
|
|
106
|
+
function extensionParams(operation, decode) {
|
|
107
|
+
return (params) => {
|
|
108
|
+
try {
|
|
109
|
+
if (params && typeof params === 'object' && !Array.isArray(params) && '_meta' in params) {
|
|
110
|
+
const record = params;
|
|
111
|
+
const meta = record._meta;
|
|
112
|
+
if (meta !== undefined &&
|
|
113
|
+
meta !== null &&
|
|
114
|
+
(typeof meta !== 'object' || Array.isArray(meta))) {
|
|
115
|
+
throw new Error('Invalid ACP request metadata');
|
|
116
|
+
}
|
|
117
|
+
const { _meta: _ignored, ...domainParams } = record;
|
|
118
|
+
return decode(domainParams);
|
|
119
|
+
}
|
|
120
|
+
return decode(params);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
throw RequestError.invalidParams({ source: 'adapter', operation, code: 'invalid_request' }, `Invalid ${operation} request`);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
92
126
|
}
|
|
93
127
|
function sessionContext(client, signal, capabilities, turnStatus = false) {
|
|
94
128
|
return {
|
|
@@ -104,5 +138,11 @@ function sessionContext(client, signal, capabilities, turnStatus = false) {
|
|
|
104
138
|
notifyTurnStatus: (status) => client.notify('_maka/turn/status', status),
|
|
105
139
|
}
|
|
106
140
|
: {}),
|
|
141
|
+
...(capabilities._meta?.['_maka/goalPlanStatus'] === true
|
|
142
|
+
? {
|
|
143
|
+
notifyGoalStatus: (status) => client.notify('_maka/goal/status', status),
|
|
144
|
+
notifyPlanChanged: (status) => client.notify('_maka/plan/changed', status),
|
|
145
|
+
}
|
|
146
|
+
: {}),
|
|
107
147
|
};
|
|
108
148
|
}
|
|
@@ -0,0 +1,185 @@
|
|
|
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
|
+
/** Latest authoritative domain state for one retained Session attachment. */
|
|
20
|
+
export class AcpSessionDomainObservation {
|
|
21
|
+
#sessionId;
|
|
22
|
+
#queryPlan;
|
|
23
|
+
#goalNotify;
|
|
24
|
+
#planNotify;
|
|
25
|
+
#disposed = false;
|
|
26
|
+
#epoch = 0;
|
|
27
|
+
#goal = null;
|
|
28
|
+
#goalKnown = false;
|
|
29
|
+
#goalDelivered;
|
|
30
|
+
#planDelivered;
|
|
31
|
+
#planSeen;
|
|
32
|
+
#planDirty = false;
|
|
33
|
+
#planRefresh;
|
|
34
|
+
#planRetryCount = 0;
|
|
35
|
+
#planRetryTimer;
|
|
36
|
+
#goalDelivery;
|
|
37
|
+
#planDelivery;
|
|
38
|
+
#pendingGoal;
|
|
39
|
+
#pendingPlan;
|
|
40
|
+
constructor(options) {
|
|
41
|
+
this.#sessionId = options.sessionId;
|
|
42
|
+
this.#queryPlan = options.queryPlan;
|
|
43
|
+
this.#goalNotify = options.goalNotify;
|
|
44
|
+
this.#planNotify = options.planNotify;
|
|
45
|
+
}
|
|
46
|
+
initialize(goal) {
|
|
47
|
+
this.goalChanged(goal);
|
|
48
|
+
this.planChanged();
|
|
49
|
+
}
|
|
50
|
+
goalChanged(goal) {
|
|
51
|
+
if (this.#disposed)
|
|
52
|
+
return;
|
|
53
|
+
if (this.#goalKnown &&
|
|
54
|
+
goal?.goalId === this.#goal?.goalId &&
|
|
55
|
+
(goal?.revision ?? -1) < (this.#goal?.revision ?? -1))
|
|
56
|
+
return;
|
|
57
|
+
this.#goalKnown = true;
|
|
58
|
+
this.#goal = goal;
|
|
59
|
+
// Even a return to the last delivered value must replace an older pending
|
|
60
|
+
// snapshot: an in-flight notification may still change what the client sees.
|
|
61
|
+
this.#pendingGoal = { sessionId: this.#sessionId, goal };
|
|
62
|
+
this.#sendGoal();
|
|
63
|
+
}
|
|
64
|
+
planChanged() {
|
|
65
|
+
if (this.#disposed)
|
|
66
|
+
return;
|
|
67
|
+
if (!this.#planNotify())
|
|
68
|
+
return;
|
|
69
|
+
if (this.#planRetryTimer) {
|
|
70
|
+
clearTimeout(this.#planRetryTimer);
|
|
71
|
+
this.#planRetryTimer = undefined;
|
|
72
|
+
}
|
|
73
|
+
this.#planRetryCount = 0;
|
|
74
|
+
this.#planDirty = true;
|
|
75
|
+
this.#kickPlan();
|
|
76
|
+
}
|
|
77
|
+
#kickPlan() {
|
|
78
|
+
if (!this.#planRefresh) {
|
|
79
|
+
this.#planRefresh = this.#refreshPlan().finally(() => {
|
|
80
|
+
this.#planRefresh = undefined;
|
|
81
|
+
if (this.#planDirty && !this.#disposed && !this.#planRetryTimer && this.#planRetryCount < 4)
|
|
82
|
+
this.#kickPlan();
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
canonicalReplacement(goal) {
|
|
87
|
+
this.#epoch += 1;
|
|
88
|
+
this.#goalDelivered = undefined;
|
|
89
|
+
this.#planDelivered = undefined;
|
|
90
|
+
this.#planSeen = undefined;
|
|
91
|
+
this.#goalKnown = false;
|
|
92
|
+
this.initialize(goal);
|
|
93
|
+
}
|
|
94
|
+
dispose() {
|
|
95
|
+
this.#disposed = true;
|
|
96
|
+
this.#epoch += 1;
|
|
97
|
+
this.#pendingGoal = undefined;
|
|
98
|
+
this.#pendingPlan = undefined;
|
|
99
|
+
this.#planDirty = false;
|
|
100
|
+
if (this.#planRetryTimer)
|
|
101
|
+
clearTimeout(this.#planRetryTimer);
|
|
102
|
+
this.#planRetryTimer = undefined;
|
|
103
|
+
}
|
|
104
|
+
async #refreshPlan() {
|
|
105
|
+
// A frame received during a read is folded into one follow-up read.
|
|
106
|
+
while (this.#planDirty && !this.#disposed) {
|
|
107
|
+
this.#planDirty = false;
|
|
108
|
+
const epoch = this.#epoch;
|
|
109
|
+
try {
|
|
110
|
+
const result = await this.#queryPlan();
|
|
111
|
+
if (this.#disposed || epoch !== this.#epoch || result.kind !== 'page')
|
|
112
|
+
continue;
|
|
113
|
+
this.#planRetryCount = 0;
|
|
114
|
+
if (this.#planSeen !== undefined && result.storeVersion < this.#planSeen)
|
|
115
|
+
continue;
|
|
116
|
+
this.#planSeen = result.storeVersion;
|
|
117
|
+
const status = {
|
|
118
|
+
sessionId: this.#sessionId,
|
|
119
|
+
storeVersion: result.storeVersion,
|
|
120
|
+
latestProposalId: result.latestProposalId,
|
|
121
|
+
activeExecutionId: result.activeExecutionId,
|
|
122
|
+
};
|
|
123
|
+
if (result.storeVersion !== this.#planDelivered) {
|
|
124
|
+
this.#pendingPlan = status;
|
|
125
|
+
this.#sendPlan();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
console.error('[acp] Plan status refresh failed:', error);
|
|
130
|
+
this.#planDirty = true;
|
|
131
|
+
this.#planRetryCount += 1;
|
|
132
|
+
if (this.#planRetryCount <= 3 && !this.#disposed) {
|
|
133
|
+
this.#planRetryTimer = setTimeout(() => {
|
|
134
|
+
this.#planRetryTimer = undefined;
|
|
135
|
+
this.#kickPlan();
|
|
136
|
+
}, 25 * 2 ** (this.#planRetryCount - 1));
|
|
137
|
+
}
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
#sendGoal() {
|
|
143
|
+
if (this.#goalDelivery || !this.#pendingGoal || this.#disposed)
|
|
144
|
+
return;
|
|
145
|
+
const notify = this.#goalNotify();
|
|
146
|
+
if (!notify)
|
|
147
|
+
return;
|
|
148
|
+
const status = this.#pendingGoal;
|
|
149
|
+
const epoch = this.#epoch;
|
|
150
|
+
this.#pendingGoal = undefined;
|
|
151
|
+
const key = JSON.stringify(status);
|
|
152
|
+
if (key === this.#goalDelivered)
|
|
153
|
+
return;
|
|
154
|
+
this.#goalDelivery = notify(status)
|
|
155
|
+
.then(() => {
|
|
156
|
+
if (!this.#disposed && epoch === this.#epoch)
|
|
157
|
+
this.#goalDelivered = key;
|
|
158
|
+
})
|
|
159
|
+
.catch((error) => console.error('[acp] Goal status delivery failed:', error))
|
|
160
|
+
.finally(() => {
|
|
161
|
+
this.#goalDelivery = undefined;
|
|
162
|
+
this.#sendGoal();
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
#sendPlan() {
|
|
166
|
+
if (this.#planDelivery || !this.#pendingPlan || this.#disposed)
|
|
167
|
+
return;
|
|
168
|
+
const notify = this.#planNotify();
|
|
169
|
+
if (!notify)
|
|
170
|
+
return;
|
|
171
|
+
const status = this.#pendingPlan;
|
|
172
|
+
const epoch = this.#epoch;
|
|
173
|
+
this.#pendingPlan = undefined;
|
|
174
|
+
this.#planDelivery = notify(status)
|
|
175
|
+
.then(() => {
|
|
176
|
+
if (!this.#disposed && epoch === this.#epoch)
|
|
177
|
+
this.#planDelivered = status.storeVersion;
|
|
178
|
+
})
|
|
179
|
+
.catch((error) => console.error('[acp] Plan status delivery failed:', error))
|
|
180
|
+
.finally(() => {
|
|
181
|
+
this.#planDelivery = undefined;
|
|
182
|
+
this.#sendPlan();
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -35,7 +35,7 @@ export class AcpSessionEventMapper {
|
|
|
35
35
|
this.#sessionId = options.sessionId;
|
|
36
36
|
this.#notify = options.notify;
|
|
37
37
|
this.#signal = options.signal;
|
|
38
|
-
this.#tools = new AcpToolEventMapper((update) => this.#deliver({ sessionId: this.#sessionId, update }));
|
|
38
|
+
this.#tools = new AcpToolEventMapper((update) => this.#deliver({ sessionId: this.#sessionId, update }), this.#sessionId);
|
|
39
39
|
}
|
|
40
40
|
accept(event) {
|
|
41
41
|
return this.#enqueue(async () => {
|