@moxxy/mode-goal 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/completion.d.ts +20 -0
- package/dist/completion.d.ts.map +1 -0
- package/dist/completion.js +48 -0
- package/dist/completion.js.map +1 -0
- package/dist/constants.d.ts +50 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +65 -0
- package/dist/constants.js.map +1 -0
- package/dist/goal-loop.d.ts +34 -0
- package/dist/goal-loop.d.ts.map +1 -0
- package/dist/goal-loop.js +454 -0
- package/dist/goal-loop.js.map +1 -0
- package/dist/goal-tools.d.ts +14 -0
- package/dist/goal-tools.d.ts.map +1 -0
- package/dist/goal-tools.js +55 -0
- package/dist/goal-tools.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
- package/src/completion.test.ts +68 -0
- package/src/completion.ts +55 -0
- package/src/constants.ts +75 -0
- package/src/goal-loop.ts +523 -0
- package/src/goal-tools.ts +61 -0
- package/src/index.test.ts +577 -0
- package/src/index.ts +28 -0
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import { buildSystemPromptWithSkills, collectProviderStream, createStuckLoopDetector, emitRequestsAndDetectStuck, executeToolUses, isContextOverflowError, nextBackoffMs, projectMessages, runCompactionIfNeeded, runElisionIfNeeded, sleepWithAbort, usageEventFields, } from '@moxxy/sdk';
|
|
2
|
+
import { detectGoalTerminal } from './completion.js';
|
|
3
|
+
import { CONTINUE_NUDGE, GOAL_ABANDON_TOOL, GOAL_COMPLETE_TOOL, GOAL_MAX_ITERATIONS, GOAL_MAX_NOOP_ITERATIONS, GOAL_MODE_NAME, GOAL_PLUGIN_ID, GOAL_SYSTEM_PROMPT, GOAL_TOKEN_BUDGET, MAX_CONSECUTIVE_RETRIES, STALL_NUDGE, } from './constants.js';
|
|
4
|
+
/** Exponential back-off base/cap for the retry schedule (attempt is 1-based). */
|
|
5
|
+
const RETRY_BACKOFF_BASE_MS = 500;
|
|
6
|
+
const RETRY_BACKOFF_CAP_MS = 30_000;
|
|
7
|
+
// Abort-aware sleep, injectable for tests so the back-off path runs instantly
|
|
8
|
+
// and deterministically. Production delegates to the SDK's sleepWithAbort: a
|
|
9
|
+
// real timer that clears (and drops its abort listener) when the signal fires,
|
|
10
|
+
// so a pending back-off never outlives a cancelled goal run.
|
|
11
|
+
let sleepImpl = (ms, signal) => sleepWithAbort(ms, signal);
|
|
12
|
+
/**
|
|
13
|
+
* Override the retry back-off sleep (test seam). Returns a restore fn that
|
|
14
|
+
* callers MUST invoke (in a `finally`) — `sleepImpl` is a module-scoped
|
|
15
|
+
* singleton shared process-wide, so a leaked override bleeds the fake sleep
|
|
16
|
+
* into every other turn/test running in the same worker. Test-only.
|
|
17
|
+
*/
|
|
18
|
+
export function __setRetrySleepForTests(fn) {
|
|
19
|
+
const prev = sleepImpl;
|
|
20
|
+
sleepImpl = fn;
|
|
21
|
+
return () => {
|
|
22
|
+
sleepImpl = prev;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Goal mode driver.
|
|
27
|
+
*
|
|
28
|
+
* Unlike tool-use (which returns the instant the model stops emitting tools),
|
|
29
|
+
* goal mode treats "stopped emitting tools" as a cue to re-prompt: it keeps
|
|
30
|
+
* the model working autonomously across iterations until the model explicitly
|
|
31
|
+
* calls `goal_complete` (success) or `goal_abandon` (blocked). Every iteration
|
|
32
|
+
* is guarded so the loop always terminates:
|
|
33
|
+
*
|
|
34
|
+
* - hard iteration cap (`maxIterations`)
|
|
35
|
+
* - cumulative token budget
|
|
36
|
+
* - stuck-loop detector (same identical-call detection as tool-use)
|
|
37
|
+
* - no-progress detection (repeated idle iterations → stall stop)
|
|
38
|
+
* - `ctx.signal.aborted` checked every iteration and mid tool batch
|
|
39
|
+
*
|
|
40
|
+
* Tool calls are auto-approved for the whole run (the user opted into full
|
|
41
|
+
* autonomy) by swapping in a resolver that replaces only the PROMPT path:
|
|
42
|
+
* the session resolver's prompt-free `policyCheck` (user deny/allow rules
|
|
43
|
+
* from ~/.moxxy/permissions.json plus tool-declared rules) is consulted
|
|
44
|
+
* first, so a configured deny rule still denies here. Every call also still
|
|
45
|
+
* flows through `dispatchToolCall`, so tool-call HOOKS (e.g. a security
|
|
46
|
+
* plugin) still run and can deny. Auto-approve skips the prompt, not the
|
|
47
|
+
* policy.
|
|
48
|
+
*/
|
|
49
|
+
export async function* runGoalMode(ctx) {
|
|
50
|
+
if (ctx.signal.aborted) {
|
|
51
|
+
yield await ctx.emit({
|
|
52
|
+
type: 'abort',
|
|
53
|
+
sessionId: ctx.sessionId,
|
|
54
|
+
turnId: ctx.turnId,
|
|
55
|
+
source: 'system',
|
|
56
|
+
reason: 'aborted before goal mode start',
|
|
57
|
+
});
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Auto-approve for the duration of the run — the user chose to let goal
|
|
61
|
+
// mode run unattended, so nothing may ever block on an interactive prompt.
|
|
62
|
+
// But ONLY the prompt is skipped: the session resolver's prompt-free
|
|
63
|
+
// `policyCheck` (deny/allow rules from ~/.moxxy/permissions.json, then the
|
|
64
|
+
// tool's own declared rule) is consulted first, so a user deny rule still
|
|
65
|
+
// denies in goal mode. Anything the policy doesn't decide is allowed.
|
|
66
|
+
// Scoped to goalCtx so it never leaks past this loop.
|
|
67
|
+
const sessionResolver = ctx.permissions;
|
|
68
|
+
const autoApprove = {
|
|
69
|
+
name: 'goal-auto-approve',
|
|
70
|
+
check: async (call, permCtx) => {
|
|
71
|
+
const policy = (await sessionResolver.policyCheck?.(call, permCtx)) ?? null;
|
|
72
|
+
if (policy)
|
|
73
|
+
return policy;
|
|
74
|
+
return { mode: 'allow', reason: 'goal mode runs tools unattended (auto-approve)' };
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
const goalCtx = {
|
|
78
|
+
...ctx,
|
|
79
|
+
systemPrompt: composeSystemPrompts(ctx.systemPrompt, GOAL_SYSTEM_PROMPT),
|
|
80
|
+
permissions: autoApprove,
|
|
81
|
+
};
|
|
82
|
+
yield await ctx.emit({
|
|
83
|
+
type: 'plugin_event',
|
|
84
|
+
sessionId: ctx.sessionId,
|
|
85
|
+
turnId: ctx.turnId,
|
|
86
|
+
source: 'plugin',
|
|
87
|
+
pluginId: GOAL_PLUGIN_ID,
|
|
88
|
+
subtype: 'goal_started',
|
|
89
|
+
payload: { autoApprove: true, maxIterations: ctx.maxIterations ?? GOAL_MAX_ITERATIONS },
|
|
90
|
+
});
|
|
91
|
+
const detector = createStuckLoopDetector(ctx.loopGuard);
|
|
92
|
+
// Coerce a caller/config-supplied bound to a positive integer. A degenerate
|
|
93
|
+
// value (0, negative, NaN, fractional) would otherwise make the for-loop
|
|
94
|
+
// never run and fall straight to the "iteration cap reached" fatal — work was
|
|
95
|
+
// never done, yet the message blames the model. The config schema validates
|
|
96
|
+
// this as a positive int, but programmatic callers (subagents/workflows)
|
|
97
|
+
// bypass that schema. Un-coercible (NaN) falls back to the default.
|
|
98
|
+
const requestedMaxIterations = ctx.maxIterations;
|
|
99
|
+
const maxIterations = typeof requestedMaxIterations === 'number' && Number.isFinite(requestedMaxIterations)
|
|
100
|
+
? Math.max(1, Math.floor(requestedMaxIterations))
|
|
101
|
+
: GOAL_MAX_ITERATIONS;
|
|
102
|
+
let noop = 0; // consecutive idle (no-tool) iterations
|
|
103
|
+
let totalTokens = 0;
|
|
104
|
+
let reactiveCompactions = 0;
|
|
105
|
+
const MAX_REACTIVE_COMPACTIONS = 2;
|
|
106
|
+
// Consecutive retryable-error count; reset on any clean provider call. Caps
|
|
107
|
+
// the busy-loop a sustained retryable condition (429 / overloaded / transient
|
|
108
|
+
// 5xx / ECONNRESET) would otherwise create — goal mode runs unattended with
|
|
109
|
+
// auto-approval, so a tight retry loop hammers the provider with nobody
|
|
110
|
+
// watching. The token budget can't backstop this: a request that errors
|
|
111
|
+
// before message_end reports no usage, so totalTokens never advances.
|
|
112
|
+
let consecutiveRetries = 0;
|
|
113
|
+
for (let iteration = 1; iteration <= maxIterations; iteration++) {
|
|
114
|
+
if (ctx.signal.aborted) {
|
|
115
|
+
yield await ctx.emit({
|
|
116
|
+
type: 'abort',
|
|
117
|
+
sessionId: ctx.sessionId,
|
|
118
|
+
turnId: ctx.turnId,
|
|
119
|
+
source: 'system',
|
|
120
|
+
reason: 'signal aborted',
|
|
121
|
+
});
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
yield await ctx.emit({
|
|
125
|
+
type: 'mode_iteration',
|
|
126
|
+
sessionId: ctx.sessionId,
|
|
127
|
+
turnId: ctx.turnId,
|
|
128
|
+
source: 'system',
|
|
129
|
+
strategy: GOAL_MODE_NAME,
|
|
130
|
+
iteration,
|
|
131
|
+
});
|
|
132
|
+
await runCompactionIfNeeded(goalCtx);
|
|
133
|
+
await runElisionIfNeeded(goalCtx);
|
|
134
|
+
// Nudge only when the model went idle last iteration (no tool calls and no
|
|
135
|
+
// completion). After a productive iteration the tool results carry the
|
|
136
|
+
// model forward on their own — no nudge needed.
|
|
137
|
+
const nudge = noop === 0 ? undefined : noop >= GOAL_MAX_NOOP_ITERATIONS - 1 ? STALL_NUDGE : CONTINUE_NUDGE;
|
|
138
|
+
const baseSystem = buildSystemPromptWithSkills(goalCtx.systemPrompt, goalCtx.skills.list()) ?? '';
|
|
139
|
+
const { messages, stablePrefixIndex } = projectMessages(goalCtx, {
|
|
140
|
+
...(baseSystem ? { systemPrompt: baseSystem } : {}),
|
|
141
|
+
...(nudge ? { trailingUserText: nudge } : {}),
|
|
142
|
+
});
|
|
143
|
+
yield await ctx.emit({
|
|
144
|
+
type: 'provider_request',
|
|
145
|
+
sessionId: ctx.sessionId,
|
|
146
|
+
turnId: ctx.turnId,
|
|
147
|
+
source: 'system',
|
|
148
|
+
provider: goalCtx.provider.name,
|
|
149
|
+
model: goalCtx.model,
|
|
150
|
+
});
|
|
151
|
+
const { text, toolUses, stopReason, error, usage, reasoning } = await collectProviderStream(goalCtx, messages, {
|
|
152
|
+
iteration,
|
|
153
|
+
stablePrefixIndex,
|
|
154
|
+
// The nudge is volatile — injected for this call only, never appended
|
|
155
|
+
// to the log — so the cache strategy must keep its rolling tail
|
|
156
|
+
// breakpoint BEFORE it. Otherwise every idle iteration caches a
|
|
157
|
+
// prefix ending in a message that won't exist at that position next
|
|
158
|
+
// call: a guaranteed-wasted cache write.
|
|
159
|
+
...(nudge ? { volatileTailCount: 1 } : {}),
|
|
160
|
+
});
|
|
161
|
+
yield await ctx.emit({
|
|
162
|
+
type: 'provider_response',
|
|
163
|
+
sessionId: ctx.sessionId,
|
|
164
|
+
turnId: ctx.turnId,
|
|
165
|
+
source: 'system',
|
|
166
|
+
provider: goalCtx.provider.name,
|
|
167
|
+
model: goalCtx.model,
|
|
168
|
+
...usageEventFields(usage),
|
|
169
|
+
});
|
|
170
|
+
// Tally the FULL prompt of each call. Anthropic reports the cached portion
|
|
171
|
+
// separately (`inputTokens` is only the non-cached prefix), so on a long
|
|
172
|
+
// goal run — where the rolling cache breakpoint serves most of the prompt
|
|
173
|
+
// as cacheRead — counting input+output alone undercounts by a large factor
|
|
174
|
+
// and the runaway-budget backstop could be exceeded many times over before
|
|
175
|
+
// it trips. Include both cache fields so the budget reflects real usage.
|
|
176
|
+
if (usage)
|
|
177
|
+
totalTokens +=
|
|
178
|
+
(usage.inputTokens ?? 0) +
|
|
179
|
+
(usage.cacheReadTokens ?? 0) +
|
|
180
|
+
(usage.cacheCreationTokens ?? 0) +
|
|
181
|
+
(usage.outputTokens ?? 0);
|
|
182
|
+
if (error) {
|
|
183
|
+
const overflow = isContextOverflowError(error.message);
|
|
184
|
+
if (overflow && reactiveCompactions < MAX_REACTIVE_COMPACTIONS) {
|
|
185
|
+
const compacted = await runCompactionIfNeeded(goalCtx, { force: true });
|
|
186
|
+
if (compacted) {
|
|
187
|
+
// Only count an attempt that actually compacted — a no-op (overflow
|
|
188
|
+
// lives in the un-compactable recent tail) must not deny a later,
|
|
189
|
+
// genuinely compactable overflow its retry.
|
|
190
|
+
reactiveCompactions += 1;
|
|
191
|
+
yield await ctx.emit({
|
|
192
|
+
type: 'error',
|
|
193
|
+
sessionId: ctx.sessionId,
|
|
194
|
+
turnId: ctx.turnId,
|
|
195
|
+
source: 'system',
|
|
196
|
+
kind: 'retryable',
|
|
197
|
+
message: 'context window exceeded — compacted older turns, retrying',
|
|
198
|
+
});
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// A context overflow that can't be compacted further is fatal regardless
|
|
203
|
+
// of the provider's `retryable` flag: some providers mark "reduce the
|
|
204
|
+
// length" errors retryable, but the prompt cannot shrink, so a retry just
|
|
205
|
+
// re-sends the identical over-budget request and overflows again — a tight
|
|
206
|
+
// loop bounded only by maxIterations (the request errors before usage is
|
|
207
|
+
// reported, so the token budget never advances to stop it).
|
|
208
|
+
const fatal = !error.retryable || overflow;
|
|
209
|
+
if (fatal) {
|
|
210
|
+
yield await ctx.emit({
|
|
211
|
+
type: 'error',
|
|
212
|
+
sessionId: ctx.sessionId,
|
|
213
|
+
turnId: ctx.turnId,
|
|
214
|
+
source: 'system',
|
|
215
|
+
kind: 'fatal',
|
|
216
|
+
message: `goal: ${error.message}`,
|
|
217
|
+
});
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
// Retryable: surface it, then back off before retrying. A persistent
|
|
221
|
+
// retryable condition (sustained 429 / outage) must NOT busy-loop the
|
|
222
|
+
// provider — give up with a fatal error after the bounded retry count.
|
|
223
|
+
// The counter resets on any clean provider call, so a long run can still
|
|
224
|
+
// recover from transient blips.
|
|
225
|
+
consecutiveRetries += 1;
|
|
226
|
+
yield await ctx.emit({
|
|
227
|
+
type: 'error',
|
|
228
|
+
sessionId: ctx.sessionId,
|
|
229
|
+
turnId: ctx.turnId,
|
|
230
|
+
source: 'system',
|
|
231
|
+
kind: 'retryable',
|
|
232
|
+
message: `goal: ${error.message}`,
|
|
233
|
+
});
|
|
234
|
+
if (consecutiveRetries >= MAX_CONSECUTIVE_RETRIES) {
|
|
235
|
+
yield await ctx.emit({
|
|
236
|
+
type: 'error',
|
|
237
|
+
sessionId: ctx.sessionId,
|
|
238
|
+
turnId: ctx.turnId,
|
|
239
|
+
source: 'system',
|
|
240
|
+
kind: 'fatal',
|
|
241
|
+
message: `goal: provider kept returning a retryable error ${consecutiveRetries} times in a row ` +
|
|
242
|
+
`(last: ${error.message}); giving up rather than hammering the provider.`,
|
|
243
|
+
});
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
await sleepImpl(nextBackoffMs(consecutiveRetries, RETRY_BACKOFF_BASE_MS, RETRY_BACKOFF_CAP_MS), ctx.signal);
|
|
247
|
+
if (ctx.signal.aborted) {
|
|
248
|
+
yield await ctx.emit({
|
|
249
|
+
type: 'abort',
|
|
250
|
+
sessionId: ctx.sessionId,
|
|
251
|
+
turnId: ctx.turnId,
|
|
252
|
+
source: 'system',
|
|
253
|
+
reason: 'signal aborted during retry back-off',
|
|
254
|
+
});
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
// Clean provider call — reset the overflow-recovery + retry budgets.
|
|
260
|
+
reactiveCompactions = 0;
|
|
261
|
+
consecutiveRetries = 0;
|
|
262
|
+
// Finalize the reasoning summary for THIS call before any exit decision or
|
|
263
|
+
// tool/assistant emit, so the log order is reasoning → tool_use → text
|
|
264
|
+
// (projection attaches the signed thinking block as content[0] of the same
|
|
265
|
+
// turn). Emitting it ahead of the budget backstop keeps every exit path
|
|
266
|
+
// consistent — the budget-exhausting call's reasoning is logged just like
|
|
267
|
+
// any other call's, rather than being silently dropped at this exit.
|
|
268
|
+
if (reasoning) {
|
|
269
|
+
yield await ctx.emit({
|
|
270
|
+
type: 'reasoning_message',
|
|
271
|
+
sessionId: ctx.sessionId,
|
|
272
|
+
turnId: ctx.turnId,
|
|
273
|
+
source: 'model',
|
|
274
|
+
content: reasoning.text,
|
|
275
|
+
...(reasoning.signature ? { signature: reasoning.signature } : {}),
|
|
276
|
+
...(reasoning.redacted ? { redacted: true } : {}),
|
|
277
|
+
...(reasoning.encrypted ? { encrypted: reasoning.encrypted } : {}),
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
// Token budget backstop (alongside the iteration cap).
|
|
281
|
+
if (totalTokens > GOAL_TOKEN_BUDGET) {
|
|
282
|
+
// Persist the budget-exhausting call's assistant text before exiting,
|
|
283
|
+
// just like every productive iteration does (the assistant emit below
|
|
284
|
+
// this block is skipped by the `return`). Otherwise the model's last
|
|
285
|
+
// words vanish from the log, so a resume ("continue from here") loses
|
|
286
|
+
// that context. We do NOT execute its tool calls — the run is stopping.
|
|
287
|
+
if (text || stopReason === 'end_turn' || toolUses.length === 0) {
|
|
288
|
+
yield await ctx.emit({
|
|
289
|
+
type: 'assistant_message',
|
|
290
|
+
sessionId: ctx.sessionId,
|
|
291
|
+
turnId: ctx.turnId,
|
|
292
|
+
source: 'model',
|
|
293
|
+
content: text,
|
|
294
|
+
stopReason,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
yield await ctx.emit({
|
|
298
|
+
type: 'plugin_event',
|
|
299
|
+
sessionId: ctx.sessionId,
|
|
300
|
+
turnId: ctx.turnId,
|
|
301
|
+
source: 'plugin',
|
|
302
|
+
pluginId: GOAL_PLUGIN_ID,
|
|
303
|
+
subtype: 'goal_budget_exhausted',
|
|
304
|
+
payload: { totalTokens, budget: GOAL_TOKEN_BUDGET, iteration },
|
|
305
|
+
});
|
|
306
|
+
yield await ctx.emit({
|
|
307
|
+
type: 'assistant_message',
|
|
308
|
+
sessionId: ctx.sessionId,
|
|
309
|
+
turnId: ctx.turnId,
|
|
310
|
+
source: 'system',
|
|
311
|
+
content: `Goal mode stopped: token budget exhausted (${totalTokens.toLocaleString()} > ` +
|
|
312
|
+
`${GOAL_TOKEN_BUDGET.toLocaleString()}) before the goal was completed. ` +
|
|
313
|
+
`Send another message to continue from here.`,
|
|
314
|
+
stopReason: 'end_turn',
|
|
315
|
+
});
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const stuck = yield* emitRequestsAndDetectStuck(ctx, toolUses, detector, {
|
|
319
|
+
abortedResultMessage: 'goal mode aborted (stuck pattern) before this call ran',
|
|
320
|
+
nearHint: 'against the same target (only volatile args varied)',
|
|
321
|
+
extraOnStuck: ({ toolName, count, kind }) => [
|
|
322
|
+
{
|
|
323
|
+
type: 'plugin_event',
|
|
324
|
+
sessionId: ctx.sessionId,
|
|
325
|
+
turnId: ctx.turnId,
|
|
326
|
+
source: 'plugin',
|
|
327
|
+
pluginId: GOAL_PLUGIN_ID,
|
|
328
|
+
subtype: 'goal_stuck',
|
|
329
|
+
payload: { tool: toolName, count, kind },
|
|
330
|
+
},
|
|
331
|
+
],
|
|
332
|
+
fatalMessage: ({ toolName, count, how }) => `goal mode aborted — stuck pattern: tool "${toolName}" called ${count} times ${how}. ` +
|
|
333
|
+
`The model is looping on the same call; send another message to redirect it.`,
|
|
334
|
+
});
|
|
335
|
+
if (stuck)
|
|
336
|
+
return;
|
|
337
|
+
if (text || stopReason === 'end_turn' || toolUses.length === 0) {
|
|
338
|
+
yield await ctx.emit({
|
|
339
|
+
type: 'assistant_message',
|
|
340
|
+
sessionId: ctx.sessionId,
|
|
341
|
+
turnId: ctx.turnId,
|
|
342
|
+
source: 'model',
|
|
343
|
+
content: text,
|
|
344
|
+
stopReason,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
if (toolUses.length === 0) {
|
|
348
|
+
// The model idled without calling goal_complete. Count it; nudge next
|
|
349
|
+
// iteration. After enough idle rounds, stop rather than spin forever.
|
|
350
|
+
noop += 1;
|
|
351
|
+
if (noop >= GOAL_MAX_NOOP_ITERATIONS) {
|
|
352
|
+
yield await ctx.emit({
|
|
353
|
+
type: 'plugin_event',
|
|
354
|
+
sessionId: ctx.sessionId,
|
|
355
|
+
turnId: ctx.turnId,
|
|
356
|
+
source: 'plugin',
|
|
357
|
+
pluginId: GOAL_PLUGIN_ID,
|
|
358
|
+
subtype: 'goal_stalled',
|
|
359
|
+
payload: { idleIterations: noop, iteration },
|
|
360
|
+
});
|
|
361
|
+
yield await ctx.emit({
|
|
362
|
+
type: 'assistant_message',
|
|
363
|
+
sessionId: ctx.sessionId,
|
|
364
|
+
turnId: ctx.turnId,
|
|
365
|
+
source: 'system',
|
|
366
|
+
content: 'Goal mode stopped: the model went idle without calling `goal_complete`. ' +
|
|
367
|
+
'It may believe the goal is done — review the work above, and send another message to continue if not.',
|
|
368
|
+
stopReason: 'end_turn',
|
|
369
|
+
});
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
noop = 0;
|
|
375
|
+
const exited = yield* executeToolUses(goalCtx, toolUses, iteration);
|
|
376
|
+
if (exited)
|
|
377
|
+
return;
|
|
378
|
+
// Did this batch end the run? (goal_complete / goal_abandon, confirmed via
|
|
379
|
+
// a successful tool_result in the log.) Only materialise the log (an O(n)
|
|
380
|
+
// copy of the ever-growing append-only log) when the batch actually used a
|
|
381
|
+
// goal tool — otherwise this ran on every productive iteration, O(n²) per
|
|
382
|
+
// run. detectGoalTerminal itself early-returns null on an empty batch, so a
|
|
383
|
+
// batch with no goal tools needs no log at all.
|
|
384
|
+
const hasGoalTool = toolUses.some((t) => t.name === GOAL_COMPLETE_TOOL || t.name === GOAL_ABANDON_TOOL);
|
|
385
|
+
const terminal = hasGoalTool ? detectGoalTerminal(ctx.log.slice(), toolUses) : null;
|
|
386
|
+
if (terminal?.kind === 'complete') {
|
|
387
|
+
yield await ctx.emit({
|
|
388
|
+
type: 'plugin_event',
|
|
389
|
+
sessionId: ctx.sessionId,
|
|
390
|
+
turnId: ctx.turnId,
|
|
391
|
+
source: 'plugin',
|
|
392
|
+
pluginId: GOAL_PLUGIN_ID,
|
|
393
|
+
subtype: 'goal_completed',
|
|
394
|
+
payload: { summary: terminal.summary, evidenceCount: terminal.evidence.length, iterations: iteration },
|
|
395
|
+
});
|
|
396
|
+
const evidenceBlock = terminal.evidence.length > 0 ? `\n\n${terminal.evidence.map((e) => `- ${e}`).join('\n')}` : '';
|
|
397
|
+
yield await ctx.emit({
|
|
398
|
+
type: 'assistant_message',
|
|
399
|
+
sessionId: ctx.sessionId,
|
|
400
|
+
turnId: ctx.turnId,
|
|
401
|
+
source: 'system',
|
|
402
|
+
content: `✓ Goal complete — ${terminal.summary}${evidenceBlock}`,
|
|
403
|
+
stopReason: 'end_turn',
|
|
404
|
+
});
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (terminal?.kind === 'abandon') {
|
|
408
|
+
yield await ctx.emit({
|
|
409
|
+
type: 'plugin_event',
|
|
410
|
+
sessionId: ctx.sessionId,
|
|
411
|
+
turnId: ctx.turnId,
|
|
412
|
+
source: 'plugin',
|
|
413
|
+
pluginId: GOAL_PLUGIN_ID,
|
|
414
|
+
subtype: 'goal_abandoned',
|
|
415
|
+
payload: { reason: terminal.reason, ...(terminal.needsFromUser ? { needsFromUser: terminal.needsFromUser } : {}), iterations: iteration },
|
|
416
|
+
});
|
|
417
|
+
const needs = terminal.needsFromUser ? `\n\nNeeds from you: ${terminal.needsFromUser}` : '';
|
|
418
|
+
yield await ctx.emit({
|
|
419
|
+
type: 'assistant_message',
|
|
420
|
+
sessionId: ctx.sessionId,
|
|
421
|
+
turnId: ctx.turnId,
|
|
422
|
+
source: 'system',
|
|
423
|
+
content: `Goal abandoned — ${terminal.reason}${needs}`,
|
|
424
|
+
stopReason: 'end_turn',
|
|
425
|
+
});
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
// Iteration cap hit without the model declaring done.
|
|
430
|
+
yield await ctx.emit({
|
|
431
|
+
type: 'plugin_event',
|
|
432
|
+
sessionId: ctx.sessionId,
|
|
433
|
+
turnId: ctx.turnId,
|
|
434
|
+
source: 'plugin',
|
|
435
|
+
pluginId: GOAL_PLUGIN_ID,
|
|
436
|
+
subtype: 'goal_max_iterations',
|
|
437
|
+
payload: { maxIterations },
|
|
438
|
+
});
|
|
439
|
+
yield await ctx.emit({
|
|
440
|
+
type: 'error',
|
|
441
|
+
sessionId: ctx.sessionId,
|
|
442
|
+
turnId: ctx.turnId,
|
|
443
|
+
source: 'system',
|
|
444
|
+
kind: 'fatal',
|
|
445
|
+
message: `goal mode reached the iteration cap (${maxIterations}) without calling goal_complete. ` +
|
|
446
|
+
`Stopping to avoid an unbounded run; send another message to continue.`,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
function composeSystemPrompts(user, layer) {
|
|
450
|
+
if (!user || user.trim() === '')
|
|
451
|
+
return layer;
|
|
452
|
+
return `${layer}\n\n---\n\n${user}`;
|
|
453
|
+
}
|
|
454
|
+
//# sourceMappingURL=goal-loop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"goal-loop.js","sourceRoot":"","sources":["../src/goal-loop.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The goal "utility": two side-effect-free tools the model calls to control the
|
|
3
|
+
* goal loop. They don't DO anything — calling them is the signal. The loop in
|
|
4
|
+
* `goal-loop.ts` watches for their (successful) tool_result and terminates.
|
|
5
|
+
*
|
|
6
|
+
* They're registered globally by the plugin, so they exist in every mode, but
|
|
7
|
+
* only goal mode's system prompt tells the model to use them; elsewhere they're
|
|
8
|
+
* inert. `permission: { action: 'allow' }` means they never trip a permission
|
|
9
|
+
* prompt — declaring done is not a privileged action.
|
|
10
|
+
*/
|
|
11
|
+
export declare const goalCompleteTool: import("@moxxy/sdk").ToolDef;
|
|
12
|
+
export declare const goalAbandonTool: import("@moxxy/sdk").ToolDef;
|
|
13
|
+
export declare const goalTools: import("@moxxy/sdk").ToolDef[];
|
|
14
|
+
//# sourceMappingURL=goal-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"goal-tools.d.ts","sourceRoot":"","sources":["../src/goal-tools.ts"],"names":[],"mappings":"AAKA;;;;;;;;;GASG;AAEH,eAAO,MAAM,gBAAgB,8BAsB3B,CAAC;AAEH,eAAO,MAAM,eAAe,8BAkB1B,CAAC;AAEH,eAAO,MAAM,SAAS,gCAAsC,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { defineTool } from '@moxxy/sdk';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { GOAL_ABANDON_TOOL, GOAL_COMPLETE_TOOL } from './constants.js';
|
|
4
|
+
/**
|
|
5
|
+
* The goal "utility": two side-effect-free tools the model calls to control the
|
|
6
|
+
* goal loop. They don't DO anything — calling them is the signal. The loop in
|
|
7
|
+
* `goal-loop.ts` watches for their (successful) tool_result and terminates.
|
|
8
|
+
*
|
|
9
|
+
* They're registered globally by the plugin, so they exist in every mode, but
|
|
10
|
+
* only goal mode's system prompt tells the model to use them; elsewhere they're
|
|
11
|
+
* inert. `permission: { action: 'allow' }` means they never trip a permission
|
|
12
|
+
* prompt — declaring done is not a privileged action.
|
|
13
|
+
*/
|
|
14
|
+
export const goalCompleteTool = defineTool({
|
|
15
|
+
name: GOAL_COMPLETE_TOOL,
|
|
16
|
+
description: 'Declare the goal FULLY ACHIEVED and end goal mode. Call this only after you have verified the work. ' +
|
|
17
|
+
'Provide a short summary and concrete evidence (commands run + results, files changed, tests that passed). ' +
|
|
18
|
+
'This is the only way to end a goal-mode run successfully.',
|
|
19
|
+
inputSchema: z.object({
|
|
20
|
+
summary: z.string().min(1).describe('One- or two-sentence summary of what was accomplished.'),
|
|
21
|
+
evidence: z
|
|
22
|
+
.array(z.string())
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('Concrete proof the goal is met: commands run and their results, files changed, tests passed.'),
|
|
25
|
+
}),
|
|
26
|
+
permission: { action: 'allow' },
|
|
27
|
+
handler: (input) => {
|
|
28
|
+
const { summary, evidence } = input;
|
|
29
|
+
return {
|
|
30
|
+
acknowledged: true,
|
|
31
|
+
summary,
|
|
32
|
+
evidenceCount: evidence?.length ?? 0,
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
export const goalAbandonTool = defineTool({
|
|
37
|
+
name: GOAL_ABANDON_TOOL,
|
|
38
|
+
description: 'Give up on the goal because you are genuinely blocked — a missing credential, a destructive action you ' +
|
|
39
|
+
'should not take unattended, or a requirement too ambiguous to proceed. Provide the reason and exactly what ' +
|
|
40
|
+
'you need from the user. This ends goal mode without claiming success.',
|
|
41
|
+
inputSchema: z.object({
|
|
42
|
+
reason: z.string().min(1).describe('Why you cannot proceed.'),
|
|
43
|
+
needsFromUser: z
|
|
44
|
+
.string()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe('What the user must provide or decide for the goal to continue.'),
|
|
47
|
+
}),
|
|
48
|
+
permission: { action: 'allow' },
|
|
49
|
+
handler: (input) => {
|
|
50
|
+
const { reason, needsFromUser } = input;
|
|
51
|
+
return { acknowledged: true, reason, ...(needsFromUser ? { needsFromUser } : {}) };
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
export const goalTools = [goalCompleteTool, goalAbandonTool];
|
|
55
|
+
//# sourceMappingURL=goal-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"goal-tools.js","sourceRoot":"","sources":["../src/goal-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEvE;;;;;;;;;GASG;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAC;IACzC,IAAI,EAAE,kBAAkB;IACxB,WAAW,EACT,sGAAsG;QACtG,4GAA4G;QAC5G,2DAA2D;IAC7D,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,wDAAwD,CAAC;QAC7F,QAAQ,EAAE,CAAC;aACR,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;aACjB,QAAQ,EAAE;aACV,QAAQ,CAAC,8FAA8F,CAAC;KAC5G,CAAC;IACF,UAAU,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE;IAC/B,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;QACjB,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAiD,CAAC;QAChF,OAAO;YACL,YAAY,EAAE,IAAI;YAClB,OAAO;YACP,aAAa,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;SACrC,CAAC;IACJ,CAAC;CACF,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAC;IACxC,IAAI,EAAE,iBAAiB;IACvB,WAAW,EACT,yGAAyG;QACzG,6GAA6G;QAC7G,uEAAuE;IACzE,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,yBAAyB,CAAC;QAC7D,aAAa,EAAE,CAAC;aACb,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,gEAAgE,CAAC;KAC9E,CAAC;IACF,UAAU,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE;IAC/B,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;QACjB,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,KAAmD,CAAC;QACtF,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IACrF,CAAC;CACF,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD,eAAO,MAAM,QAAQ,8BAQnB,CAAC;AAEH,eAAO,MAAM,cAAc,6BAOzB,CAAC;AAEH,eAAe,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineMode, definePlugin } from '@moxxy/sdk';
|
|
2
|
+
import { GOAL_MODE_NAME } from './constants.js';
|
|
3
|
+
import { goalTools } from './goal-tools.js';
|
|
4
|
+
import { runGoalMode } from './goal-loop.js';
|
|
5
|
+
export { GOAL_MODE_NAME } from './constants.js';
|
|
6
|
+
export const goalMode = defineMode({
|
|
7
|
+
name: GOAL_MODE_NAME,
|
|
8
|
+
description: 'Autonomous goal loop: works across many turns until it calls goal_complete (tools auto-approved)',
|
|
9
|
+
// Goal mode auto-approves tools and keeps working unattended, so channels
|
|
10
|
+
// surface a persistent accent badge while it's active — the user must always
|
|
11
|
+
// know the agent is driving itself.
|
|
12
|
+
badge: { label: 'GOAL', tone: 'attention' },
|
|
13
|
+
run: runGoalMode,
|
|
14
|
+
});
|
|
15
|
+
export const goalModePlugin = definePlugin({
|
|
16
|
+
name: '@moxxy/mode-goal',
|
|
17
|
+
version: '0.0.0',
|
|
18
|
+
// The mode AND its control tools ship together: the loop watches for the
|
|
19
|
+
// tools' results to terminate, so they're useless apart.
|
|
20
|
+
modes: [goalMode],
|
|
21
|
+
tools: goalTools,
|
|
22
|
+
});
|
|
23
|
+
export default goalModePlugin;
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEtD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAC;IACjC,IAAI,EAAE,cAAc;IACpB,WAAW,EAAE,kGAAkG;IAC/G,0EAA0E;IAC1E,6EAA6E;IAC7E,oCAAoC;IACpC,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE;IAC3C,GAAG,EAAE,WAAW;CACjB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,YAAY,CAAC;IACzC,IAAI,EAAE,kBAAkB;IACxB,OAAO,EAAE,OAAO;IAChB,yEAAyE;IACzE,yDAAyD;IACzD,KAAK,EAAE,CAAC,QAAQ,CAAC;IACjB,KAAK,EAAE,SAAS;CACjB,CAAC,CAAC;AAEH,eAAe,cAAc,CAAC"}
|