@kasenri/dsh-orbit 0.5.10 → 0.6.1
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/README.md +47 -1
- package/lib/activation.js +7 -0
- package/lib/client.js +413 -41
- package/lib/decisions.js +4 -0
- package/lib/dsh-host.js +47 -5
- package/lib/index.js +52 -3
- package/lib/kernel.js +67 -3
- package/lib/moa-adapter.js +449 -0
- package/lib/routes.js +28 -0
- package/lib/service.js +34 -3
- package/lib/session-state.js +151 -1
- package/lib/state-store.js +13 -6
- package/lib/supervisor.js +173 -16
- package/lib/types.js +5 -1
- package/package.json +6 -2
package/lib/dsh-host.js
CHANGED
|
@@ -60,6 +60,28 @@ export class DshOrbitHost {
|
|
|
60
60
|
}
|
|
61
61
|
return this.startOneShot(parent, request, prompt, agentOptions);
|
|
62
62
|
}
|
|
63
|
+
async runModel(request) {
|
|
64
|
+
const handle = await this.startRole({
|
|
65
|
+
role: 'commander',
|
|
66
|
+
label: request.label,
|
|
67
|
+
prompt: request.prompt,
|
|
68
|
+
route: request.route,
|
|
69
|
+
toolFilter: { allow: [] },
|
|
70
|
+
...(request.signal ? { signal: request.signal } : {}),
|
|
71
|
+
});
|
|
72
|
+
try {
|
|
73
|
+
const result = await handle.result;
|
|
74
|
+
return {
|
|
75
|
+
output: result.visibleOutput ?? result.output,
|
|
76
|
+
interrupted: result.interrupted,
|
|
77
|
+
...(result.reason ? { reason: result.reason } : {}),
|
|
78
|
+
...(result.tokenUsage ? { usage: result.tokenUsage } : {}),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
await this.releaseRole(handle);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
63
85
|
parent() {
|
|
64
86
|
const initiator = this.ctx.agents.currentInitiator();
|
|
65
87
|
if (initiator)
|
|
@@ -89,6 +111,7 @@ export class DshOrbitHost {
|
|
|
89
111
|
...(value.stopReason !== 'completed' ? { reason: value.stopReason } : {}),
|
|
90
112
|
...(value.diagnostic ? { testSummary: [value.diagnostic] } : {}),
|
|
91
113
|
...(value.structured !== undefined ? { structured: value.structured } : {}),
|
|
114
|
+
...(this.readTokenUsage(run.localAgent) ? { tokenUsage: this.readTokenUsage(run.localAgent) } : {}),
|
|
92
115
|
}))
|
|
93
116
|
.catch((error) => ({
|
|
94
117
|
childId: run.id,
|
|
@@ -262,6 +285,25 @@ export class DshOrbitHost {
|
|
|
262
285
|
const events = agent.session?.snapshotEvents?.() ?? [];
|
|
263
286
|
return events.filter((event) => event.type === 'turn/start').length > previousTurns;
|
|
264
287
|
}
|
|
288
|
+
readTokenUsage(agent) {
|
|
289
|
+
const events = agent?.session?.snapshotEvents?.() ?? agent?.session?.ownEvents?.() ?? [];
|
|
290
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
291
|
+
const event = events[index];
|
|
292
|
+
if (!event || event.type !== 'assistant/message')
|
|
293
|
+
continue;
|
|
294
|
+
const usage = event.data?.usage;
|
|
295
|
+
if (typeof usage?.inputTokens !== 'number' || typeof usage.outputTokens !== 'number')
|
|
296
|
+
continue;
|
|
297
|
+
return {
|
|
298
|
+
inputTokens: usage.inputTokens,
|
|
299
|
+
outputTokens: usage.outputTokens,
|
|
300
|
+
...(typeof usage.totalTokens === 'number' ? { totalTokens: usage.totalTokens } : {}),
|
|
301
|
+
...(typeof usage.cacheReadTokens === 'number' ? { cacheReadTokens: usage.cacheReadTokens } : {}),
|
|
302
|
+
...(typeof usage.cacheWriteTokens === 'number' ? { cacheWriteTokens: usage.cacheWriteTokens } : {}),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
265
307
|
readFinalOutput(agent) {
|
|
266
308
|
const events = agent.session?.snapshotEvents?.() ?? agent.session?.ownEvents?.() ?? [];
|
|
267
309
|
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
@@ -344,8 +386,8 @@ export class DshOrbitHost {
|
|
|
344
386
|
async validateRoutes(routes, signal) {
|
|
345
387
|
const issues = [];
|
|
346
388
|
const labels = { commander: '指挥官', executor: '执行员', watchdog: '监控模型' };
|
|
347
|
-
for (const role of
|
|
348
|
-
const
|
|
389
|
+
for (const [role, route] of Object.entries(routes)) {
|
|
390
|
+
const label = labels[role] ?? role;
|
|
349
391
|
try {
|
|
350
392
|
const llm = this.ctx.reflect.get('llm');
|
|
351
393
|
if (!llm)
|
|
@@ -355,18 +397,18 @@ export class DshOrbitHost {
|
|
|
355
397
|
const info = await llm.resolveModelInfo(route.provider, route.model, activeSignal);
|
|
356
398
|
const catalog = await llm.listModels(route.provider);
|
|
357
399
|
if (catalog.length > 0 && !catalog.some((entry) => entry.id === route.model)) {
|
|
358
|
-
issues.push(`${
|
|
400
|
+
issues.push(`${label}:ORBIT_MODEL_UNAVAILABLE (${route.provider}/${route.model}),当前模型目录中不存在,请重新选择。`);
|
|
359
401
|
continue;
|
|
360
402
|
}
|
|
361
403
|
if (route.reasoningEffort !== undefined) {
|
|
362
404
|
const efforts = info.reasoning?.efforts ?? [];
|
|
363
405
|
if (!efforts.some((effort) => effort.id === route.reasoningEffort)) {
|
|
364
|
-
issues.push(`${
|
|
406
|
+
issues.push(`${label}:ORBIT_REASONING_EFFORT_UNAVAILABLE (${route.provider}/${route.model}/${route.reasoningEffort}),当前模型未声明此推理等级。`);
|
|
365
407
|
}
|
|
366
408
|
}
|
|
367
409
|
}
|
|
368
410
|
catch (error) {
|
|
369
|
-
issues.push(`${
|
|
411
|
+
issues.push(`${label}:ORBIT_MODEL_UNAVAILABLE (${route.provider}/${route.model}),当前不可用,请重新选择:${truncateSafe(error instanceof Error ? error.message : String(error), 200)}`);
|
|
370
412
|
}
|
|
371
413
|
}
|
|
372
414
|
return issues;
|
package/lib/index.js
CHANGED
|
@@ -2,12 +2,12 @@ import { AssistantStreamAccumulator, boundContextSummary, createAssistantMessage
|
|
|
2
2
|
import z from '@deepseek-ai/schemastery';
|
|
3
3
|
import { installOrbitGestureBoundary, registerOrbitCommand, registerOrbitToggleCommand } from "./activation.js";
|
|
4
4
|
import { createOrbitPreExecuteHandler } from "./pipeline-guard.js";
|
|
5
|
-
import { agentDefaultSelectionOf, resolveEffectiveRoutes, sessionModelSelectionOf, sessionModelStateOf } from "./routes.js";
|
|
5
|
+
import { agentDefaultSelectionOf, resolveEffectiveRoutes, resolveMoaPolicy, sessionModelSelectionOf, sessionModelStateOf } from "./routes.js";
|
|
6
6
|
import { installOrbitSessionProjection, orbitEnabledOf } from "./session-state.js";
|
|
7
7
|
import { OrbitService } from "./service.js";
|
|
8
8
|
import { createOrbitTool } from "./tool.js";
|
|
9
9
|
export const name = 'dsh-orbit';
|
|
10
|
-
export const inject = ['tools', 'agents', 'subagents'];
|
|
10
|
+
export const inject = ['tools', 'agents', 'subagents', 'sessions'];
|
|
11
11
|
const Route = z.object({
|
|
12
12
|
provider: z.string(),
|
|
13
13
|
model: z.string(),
|
|
@@ -19,9 +19,18 @@ const RoleRoute = z.object({
|
|
|
19
19
|
reasoningEffort: z.string().default(''),
|
|
20
20
|
});
|
|
21
21
|
/** `orbit` settings namespace: the two role routes Orbit persists itself. */
|
|
22
|
+
export const OrbitMoaSettingsSchema = z.object({
|
|
23
|
+
enabled: z.boolean().default(false),
|
|
24
|
+
candidateCount: z.natural().default(3),
|
|
25
|
+
peerCritique: z.boolean().default(false),
|
|
26
|
+
maxMoaSteps: z.natural().default(2),
|
|
27
|
+
candidates: z.array(RoleRoute).default([]),
|
|
28
|
+
judge: RoleRoute,
|
|
29
|
+
});
|
|
22
30
|
export const OrbitRouteSettingsSchema = z.object({
|
|
23
31
|
commander: RoleRoute,
|
|
24
32
|
watchdog: RoleRoute,
|
|
33
|
+
moa: OrbitMoaSettingsSchema,
|
|
25
34
|
});
|
|
26
35
|
export const Config = z.object({
|
|
27
36
|
projectDir: z.string(),
|
|
@@ -32,6 +41,7 @@ export const Config = z.object({
|
|
|
32
41
|
watchdog: Route,
|
|
33
42
|
})
|
|
34
43
|
.default({}),
|
|
44
|
+
moa: OrbitMoaSettingsSchema.default({}),
|
|
35
45
|
browserTools: z.array(z.string()).default(['agent_browser']),
|
|
36
46
|
commanderReadOnlyTools: z.array(z.string()).default(['read', 'read_image', 'glob', 'grep', 'web_search', 'web_fetch']),
|
|
37
47
|
watchdogTools: z.array(z.string()).default(['read', 'read_image', 'glob', 'grep']),
|
|
@@ -47,15 +57,37 @@ export function apply(ctx, config) {
|
|
|
47
57
|
// Orbit settings bridge. The package ships no model defaults; only explicit
|
|
48
58
|
// profile routes can act as a headless compatibility fallback.
|
|
49
59
|
let readRouteSettings = () => undefined;
|
|
60
|
+
let readMoaPrices = () => undefined;
|
|
50
61
|
ctx.inject(['settings'], (settingsCtx) => {
|
|
51
62
|
const scope = settingsCtx.settings.register('orbit', OrbitRouteSettingsSchema, {
|
|
52
63
|
base: {
|
|
53
64
|
commander: { provider: '', model: '', reasoningEffort: '' },
|
|
54
65
|
watchdog: { provider: '', model: '', reasoningEffort: '' },
|
|
66
|
+
moa: {
|
|
67
|
+
enabled: config.moa.enabled ?? false,
|
|
68
|
+
candidateCount: config.moa.candidateCount ?? 3,
|
|
69
|
+
peerCritique: config.moa.peerCritique ?? false,
|
|
70
|
+
maxMoaSteps: config.moa.maxMoaSteps ?? 2,
|
|
71
|
+
candidates: (config.moa.candidates ?? []).map((route) => ({
|
|
72
|
+
provider: route.provider,
|
|
73
|
+
model: route.model,
|
|
74
|
+
reasoningEffort: route.reasoningEffort ?? '',
|
|
75
|
+
})),
|
|
76
|
+
judge: config.moa.judge
|
|
77
|
+
? { provider: config.moa.judge.provider, model: config.moa.judge.model, reasoningEffort: config.moa.judge.reasoningEffort ?? '' }
|
|
78
|
+
: { provider: '', model: '', reasoningEffort: '' },
|
|
79
|
+
},
|
|
55
80
|
},
|
|
56
81
|
});
|
|
57
82
|
readRouteSettings = () => scope.get();
|
|
58
|
-
|
|
83
|
+
readMoaPrices = () => {
|
|
84
|
+
const value = settingsCtx.settings.get('dsh-moa');
|
|
85
|
+
return value?.prices;
|
|
86
|
+
};
|
|
87
|
+
settingsCtx.effect(() => () => {
|
|
88
|
+
readRouteSettings = () => undefined;
|
|
89
|
+
readMoaPrices = () => undefined;
|
|
90
|
+
});
|
|
59
91
|
});
|
|
60
92
|
// A NEW run resolves its three routes exactly once: Commander/Watchdog from
|
|
61
93
|
// the Orbit settings (base = config), Executor from the initiating Session's
|
|
@@ -77,6 +109,11 @@ export function apply(ctx, config) {
|
|
|
77
109
|
};
|
|
78
110
|
// The DSH Session driving the current operation: a new run records it, and
|
|
79
111
|
// only that Session may answer the run's NEEDS_USER question.
|
|
112
|
+
const resolveMoaPolicyForRun = () => resolveMoaPolicy({
|
|
113
|
+
settings: readRouteSettings()?.moa,
|
|
114
|
+
config: config.moa,
|
|
115
|
+
...(readMoaPrices() ? { prices: readMoaPrices() } : {}),
|
|
116
|
+
});
|
|
80
117
|
const resolveOwnerSessionId = () => {
|
|
81
118
|
const agent = ctx.agents.currentInitiator();
|
|
82
119
|
return agent === undefined ? undefined : String(agent.session.id);
|
|
@@ -84,6 +121,7 @@ export function apply(ctx, config) {
|
|
|
84
121
|
const serviceConfig = {
|
|
85
122
|
routes: config.routes,
|
|
86
123
|
resolveRoutes,
|
|
124
|
+
resolveMoaPolicy: resolveMoaPolicyForRun,
|
|
87
125
|
resolveOwnerSessionId,
|
|
88
126
|
browserTools: config.browserTools,
|
|
89
127
|
commanderReadOnlyTools: config.commanderReadOnlyTools,
|
|
@@ -115,6 +153,17 @@ export function apply(ctx, config) {
|
|
|
115
153
|
});
|
|
116
154
|
installOrbitGestureBoundary(ctx, {
|
|
117
155
|
sessionEnabled: (session) => orbitEnabledOf(ctx, session),
|
|
156
|
+
competingMutationBlock: (agent, messages) => {
|
|
157
|
+
const cwd = agent.session.header.cwd ?? process.cwd();
|
|
158
|
+
if (!service.hasActiveRun(cwd))
|
|
159
|
+
return undefined;
|
|
160
|
+
const latest = [...messages].reverse().find((message) => message.source.kind === 'user');
|
|
161
|
+
const text = latest?.content.filter((block) => block.type === 'text').map((block) => block.text).join('\n').trimStart() ?? '';
|
|
162
|
+
return /^\/moa(?=$|[\t\n\r ])/u.test(text)
|
|
163
|
+
? 'ORBIT_MUTATION_DRIVER_CONFLICT: 当前 workspace 已由 Orbit 持有,不能同时启动独立 /moa。请先完成或停止当前 Orbit Run。'
|
|
164
|
+
: undefined;
|
|
165
|
+
},
|
|
166
|
+
onBlocked: (agent, reason) => appendOrbitNotice(agent.session, reason),
|
|
118
167
|
activate: async (agent, goal, position, signal) => {
|
|
119
168
|
const cwd = agent.session.header.cwd ?? process.cwd();
|
|
120
169
|
let result;
|
package/lib/kernel.js
CHANGED
|
@@ -5,11 +5,60 @@
|
|
|
5
5
|
* read a clock, or use randomness. Runtime values (routes, timestamps, run ids)
|
|
6
6
|
* are supplied by the supervisor, which owns orchestration and persistence.
|
|
7
7
|
*/
|
|
8
|
-
import { MAX_CORRECTION_DEPTH, DEFAULT_LOOP_BUDGET, AUTOMATIC_LOOP_RECOVERY_RESERVE, MAX_AUTOMATIC_LOOP_BUDGET, MAX_PLAN_STEPS, MAX_WATCHDOG_CALLS_PER_STEP, MIN_PLAN_STEPS, ORBIT_SCHEMA_VERSION, } from "./types.js";
|
|
8
|
+
import { MAX_CORRECTION_DEPTH, DEFAULT_LOOP_BUDGET, AUTOMATIC_LOOP_RECOVERY_RESERVE, MAX_AUTOMATIC_LOOP_BUDGET, MAX_PLAN_STEPS, MAX_WATCHDOG_CALLS_PER_STEP, MIN_PLAN_STEPS, MIN_MOA_CANDIDATES, MAX_MOA_CANDIDATES, DEFAULT_MAX_MOA_STEPS, ORBIT_SCHEMA_VERSION, } from "./types.js";
|
|
9
9
|
/** Capabilities a plan step may request. */
|
|
10
10
|
export const ORBIT_CAPABILITIES = ['filesystem', 'shell', 'web', 'browser'];
|
|
11
11
|
const STEP_CAPABILITIES = new Set(ORBIT_CAPABILITIES);
|
|
12
12
|
const PLAN_STEP_ID = /^P\d+$/u;
|
|
13
|
+
export function normalizeExecutionMode(value) {
|
|
14
|
+
return value === 'MOA' ? 'MOA' : 'SINGLE';
|
|
15
|
+
}
|
|
16
|
+
export function normalizeMoaPolicy(policy) {
|
|
17
|
+
if (policy === undefined || policy.enabled !== true)
|
|
18
|
+
return undefined;
|
|
19
|
+
if (!Number.isSafeInteger(policy.candidate_count) || policy.candidate_count < MIN_MOA_CANDIDATES || policy.candidate_count > MAX_MOA_CANDIDATES) {
|
|
20
|
+
throw new Error(`ORBIT_MOA_CANDIDATE_COUNT_INVALID: expected ${MIN_MOA_CANDIDATES}-${MAX_MOA_CANDIDATES}`);
|
|
21
|
+
}
|
|
22
|
+
if (!Number.isSafeInteger(policy.max_moa_steps) || policy.max_moa_steps < 1 || policy.max_moa_steps > MAX_PLAN_STEPS) {
|
|
23
|
+
throw new Error(`ORBIT_MOA_STEP_BUDGET_INVALID: expected 1-${MAX_PLAN_STEPS}`);
|
|
24
|
+
}
|
|
25
|
+
if (policy.candidates.length < policy.candidate_count) {
|
|
26
|
+
throw new Error('ORBIT_MOA_ROUTES_INCOMPLETE: 候选模型数量不足。');
|
|
27
|
+
}
|
|
28
|
+
const candidates = policy.candidates.slice(0, policy.candidate_count).map((route) => structuredClone(route));
|
|
29
|
+
if (candidates.some((route) => !route.provider || !route.model) || !policy.judge?.provider || !policy.judge.model) {
|
|
30
|
+
throw new Error('ORBIT_MOA_ROUTES_INCOMPLETE: 候选模型或 Judge 未完整配置。');
|
|
31
|
+
}
|
|
32
|
+
const prices = policy.prices === undefined
|
|
33
|
+
? undefined
|
|
34
|
+
: Object.fromEntries(Object.entries(policy.prices).flatMap(([key, row]) => {
|
|
35
|
+
const input = Number(row?.input);
|
|
36
|
+
const output = Number(row?.output);
|
|
37
|
+
const cacheHit = row?.cacheHit === undefined ? undefined : Number(row.cacheHit);
|
|
38
|
+
if (!Number.isFinite(input) || input < 0 || !Number.isFinite(output) || output < 0 || (cacheHit !== undefined && (!Number.isFinite(cacheHit) || cacheHit < 0)))
|
|
39
|
+
return [];
|
|
40
|
+
return [[key, { input, output, ...(cacheHit === undefined ? {} : { cacheHit }) }]];
|
|
41
|
+
}));
|
|
42
|
+
return {
|
|
43
|
+
enabled: true,
|
|
44
|
+
candidate_count: policy.candidate_count,
|
|
45
|
+
peer_critique: policy.peer_critique === true,
|
|
46
|
+
max_moa_steps: policy.max_moa_steps || DEFAULT_MAX_MOA_STEPS,
|
|
47
|
+
candidates,
|
|
48
|
+
judge: structuredClone(policy.judge),
|
|
49
|
+
...(prices && Object.keys(prices).length > 0 ? { prices } : {}),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export function assertMoaPlanWithinPolicy(plan, policy) {
|
|
53
|
+
const moaSteps = plan.steps.filter((step) => normalizeExecutionMode(step.execution_mode) === 'MOA').length;
|
|
54
|
+
if (moaSteps === 0)
|
|
55
|
+
return;
|
|
56
|
+
if (policy === undefined)
|
|
57
|
+
throw new Error('ORBIT_MOA_UNAVAILABLE: 当前 Run 未启用或未完整配置 MoA。');
|
|
58
|
+
if (moaSteps > policy.max_moa_steps) {
|
|
59
|
+
throw new Error(`ORBIT_MOA_STEP_BUDGET_EXCEEDED: plan requests ${moaSteps}, max is ${policy.max_moa_steps}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
13
62
|
export function normalizeCapabilities(value) {
|
|
14
63
|
if (!Array.isArray(value))
|
|
15
64
|
return undefined;
|
|
@@ -40,10 +89,12 @@ export function normalizePlan(plan) {
|
|
|
40
89
|
const id = PLAN_STEP_ID.test(rawId) ? rawId : `P${index}`;
|
|
41
90
|
const goal = String(record.goal ?? '').trim();
|
|
42
91
|
const capabilities = normalizeCapabilities(record.capabilities);
|
|
92
|
+
const execution_mode = normalizeExecutionMode(record.execution_mode);
|
|
43
93
|
return {
|
|
44
94
|
id,
|
|
45
95
|
goal,
|
|
46
96
|
...(capabilities ? { capabilities } : {}),
|
|
97
|
+
...(execution_mode === 'MOA' ? { execution_mode } : {}),
|
|
47
98
|
status: 'pending',
|
|
48
99
|
};
|
|
49
100
|
});
|
|
@@ -124,14 +175,16 @@ export function normalizeAppend(decision) {
|
|
|
124
175
|
const goal = String(entry.goal ?? '').trim();
|
|
125
176
|
if (goal) {
|
|
126
177
|
const capabilities = normalizeCapabilities(entry.capabilities);
|
|
127
|
-
|
|
178
|
+
const execution_mode = normalizeExecutionMode(entry.execution_mode);
|
|
179
|
+
items.push({ goal, ...(capabilities ? { capabilities } : {}), ...(execution_mode === 'MOA' ? { execution_mode } : {}) });
|
|
128
180
|
}
|
|
129
181
|
}
|
|
130
182
|
}
|
|
131
183
|
}
|
|
132
184
|
if (items.length === 0 && decision.next_step_goal?.trim()) {
|
|
133
185
|
const capabilities = normalizeCapabilities(decision.next_step_capabilities);
|
|
134
|
-
|
|
186
|
+
const execution_mode = normalizeExecutionMode(decision.next_step_execution_mode);
|
|
187
|
+
items.push({ goal: decision.next_step_goal.trim(), ...(capabilities ? { capabilities } : {}), ...(execution_mode === 'MOA' ? { execution_mode } : {}) });
|
|
135
188
|
}
|
|
136
189
|
return items;
|
|
137
190
|
}
|
|
@@ -148,6 +201,7 @@ export function correctionBlockCode(state, step) {
|
|
|
148
201
|
export function createInitialState(input) {
|
|
149
202
|
const explicit = explicitLoopBudget({ approved_loop_count: input.approvedLoopCount, max_loops: input.maxLoops });
|
|
150
203
|
const max = explicit ?? DEFAULT_LOOP_BUDGET;
|
|
204
|
+
const moaPolicy = normalizeMoaPolicy(input.moaPolicy);
|
|
151
205
|
return {
|
|
152
206
|
schema_version: ORBIT_SCHEMA_VERSION,
|
|
153
207
|
active_run_id: input.runId,
|
|
@@ -161,6 +215,7 @@ export function createInitialState(input) {
|
|
|
161
215
|
goal_hash: hashGoal(input.goal),
|
|
162
216
|
preset: input.preset ?? 'orbit-lite',
|
|
163
217
|
routes: structuredClone(input.routes),
|
|
218
|
+
...(moaPolicy ? { moa_policy: moaPolicy } : {}),
|
|
164
219
|
loop: { used: 0, max },
|
|
165
220
|
loop_budget_mode: explicit === undefined ? 'automatic' : 'explicit',
|
|
166
221
|
approved_loop_count: max,
|
|
@@ -289,6 +344,10 @@ export function applyFinalAppend(state, decision) {
|
|
|
289
344
|
const appended = normalizeAppend(decision);
|
|
290
345
|
if (appended.length === 0)
|
|
291
346
|
return 'invalid';
|
|
347
|
+
const currentMoa = state.plan.steps.filter((step) => normalizeExecutionMode(step.execution_mode) === 'MOA').length;
|
|
348
|
+
const addedMoa = appended.filter((step) => normalizeExecutionMode(step.execution_mode) === 'MOA').length;
|
|
349
|
+
if (addedMoa > 0 && (state.moa_policy === undefined || currentMoa + addedMoa > state.moa_policy.max_moa_steps))
|
|
350
|
+
return 'invalid';
|
|
292
351
|
if (remaining <= 0) {
|
|
293
352
|
enterBudgetExhausted(state);
|
|
294
353
|
return 'budget_exhausted';
|
|
@@ -301,6 +360,7 @@ export function applyFinalAppend(state, decision) {
|
|
|
301
360
|
id: `P${index}`,
|
|
302
361
|
goal: item.goal,
|
|
303
362
|
...(item.capabilities ? { capabilities: item.capabilities } : {}),
|
|
363
|
+
...(item.execution_mode === 'MOA' ? { execution_mode: 'MOA' } : {}),
|
|
304
364
|
status: 'pending',
|
|
305
365
|
});
|
|
306
366
|
}
|
|
@@ -318,10 +378,14 @@ export function applyCorrectionStep(state, step, input) {
|
|
|
318
378
|
const correctionCapabilities = input.capabilities === undefined
|
|
319
379
|
? step.capabilities
|
|
320
380
|
: normalizeCapabilities(input.capabilities);
|
|
381
|
+
const correctionMode = input.executionMode === undefined
|
|
382
|
+
? normalizeExecutionMode(step.execution_mode)
|
|
383
|
+
: normalizeExecutionMode(input.executionMode);
|
|
321
384
|
state.plan.steps.splice(insertAt, 0, {
|
|
322
385
|
id: `${base}-${number}`,
|
|
323
386
|
goal: input.nextGoal,
|
|
324
387
|
...(correctionCapabilities ? { capabilities: correctionCapabilities } : {}),
|
|
388
|
+
...(correctionMode === 'MOA' ? { execution_mode: 'MOA' } : {}),
|
|
325
389
|
status: 'pending',
|
|
326
390
|
});
|
|
327
391
|
state.phase = 'EXECUTE';
|