@moxxy/sdk 0.26.0 → 0.28.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/dist/channel.d.ts +13 -0
- package/dist/channel.d.ts.map +1 -1
- package/dist/channel.js +19 -0
- package/dist/channel.js.map +1 -1
- package/dist/event-store.d.ts +5 -1
- package/dist/event-store.d.ts.map +1 -1
- package/dist/event-store.js +24 -1
- package/dist/event-store.js.map +1 -1
- package/dist/events.d.ts +9 -3
- package/dist/events.d.ts.map +1 -1
- package/dist/first-party.d.ts +17 -0
- package/dist/first-party.d.ts.map +1 -0
- package/dist/first-party.js +19 -0
- package/dist/first-party.js.map +1 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -5
- package/dist/index.js.map +1 -1
- package/dist/isolation.d.ts +14 -0
- package/dist/isolation.d.ts.map +1 -1
- package/dist/isolation.js +75 -0
- package/dist/isolation.js.map +1 -1
- package/dist/mode/checkpoint.d.ts +107 -0
- package/dist/mode/checkpoint.d.ts.map +1 -0
- package/dist/mode/checkpoint.js +2 -0
- package/dist/mode/checkpoint.js.map +1 -0
- package/dist/mode/react-loop.d.ts +129 -0
- package/dist/mode/react-loop.d.ts.map +1 -0
- package/dist/mode/react-loop.js +480 -0
- package/dist/mode/react-loop.js.map +1 -0
- package/dist/mode/stuck-loop.d.ts +7 -0
- package/dist/mode/stuck-loop.d.ts.map +1 -1
- package/dist/mode/stuck-loop.js +4 -0
- package/dist/mode/stuck-loop.js.map +1 -1
- package/dist/mode-helpers.d.ts +4 -0
- package/dist/mode-helpers.d.ts.map +1 -1
- package/dist/mode-helpers.js +3 -0
- package/dist/mode-helpers.js.map +1 -1
- package/dist/mode.d.ts +43 -2
- package/dist/mode.d.ts.map +1 -1
- package/dist/mode.js.map +1 -1
- package/dist/plugin.d.ts +10 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/reflector.d.ts +58 -0
- package/dist/reflector.d.ts.map +1 -0
- package/dist/reflector.js +2 -0
- package/dist/reflector.js.map +1 -0
- package/dist/schemas.d.ts +215 -8
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +37 -0
- package/dist/schemas.js.map +1 -1
- package/dist/session-like.d.ts +52 -0
- package/dist/session-like.d.ts.map +1 -1
- package/dist/tool-dispatch.d.ts +35 -1
- package/dist/tool-dispatch.d.ts.map +1 -1
- package/dist/tool-dispatch.js +51 -0
- package/dist/tool-dispatch.js.map +1 -1
- package/package.json +1 -1
- package/src/channel.ts +25 -0
- package/src/event-store.ts +16 -1
- package/src/events.ts +9 -2
- package/src/exit-after-pair.test.ts +32 -0
- package/src/first-party.test.ts +26 -0
- package/src/first-party.ts +19 -0
- package/src/index.ts +32 -4
- package/src/isolation-aggregate.test.ts +69 -0
- package/src/isolation.ts +67 -0
- package/src/mode/checkpoint.ts +105 -0
- package/src/mode/react-loop.ts +694 -0
- package/src/mode/stuck-loop.ts +11 -0
- package/src/mode-helpers.ts +16 -0
- package/src/mode.ts +43 -2
- package/src/plugin.ts +10 -1
- package/src/reflector.ts +61 -0
- package/src/schemas.ts +41 -0
- package/src/session-like.ts +49 -0
- package/src/tool-dispatch.test.ts +77 -0
- package/src/tool-dispatch.ts +82 -1
package/src/isolation.ts
CHANGED
|
@@ -146,3 +146,70 @@ export const ISOLATION_RANK: Readonly<Record<IsolationStrength, number>> = Objec
|
|
|
146
146
|
wasm: 5,
|
|
147
147
|
docker: 6,
|
|
148
148
|
});
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Widest-wins union of capability declarations — the combined blast radius
|
|
152
|
+
* if every contributing tool exercised its full declared surface. Used for
|
|
153
|
+
* package-level views: `moxxy security audit --package`, the install-time
|
|
154
|
+
* capability report, and (later) signed-index capability manifests.
|
|
155
|
+
*
|
|
156
|
+
* Union semantics per axis: fs globs / env vars / commands are set-unions
|
|
157
|
+
* (sorted, deduped); net takes the strongest mode (`any` > `allowlist` with
|
|
158
|
+
* merged hosts > `none`); `subprocess` is an OR; `timeMs` / `memMb` take the
|
|
159
|
+
* max. `undefined` entries (undeclared tools) contribute nothing — callers
|
|
160
|
+
* report their count separately so a wide-open undeclared tool is never
|
|
161
|
+
* hidden behind a tidy aggregate.
|
|
162
|
+
*/
|
|
163
|
+
export function aggregateCapabilitySpecs(
|
|
164
|
+
specs: ReadonlyArray<CapabilitySpec | undefined>,
|
|
165
|
+
): CapabilitySpec {
|
|
166
|
+
const read = new Set<string>();
|
|
167
|
+
const write = new Set<string>();
|
|
168
|
+
const env = new Set<string>();
|
|
169
|
+
const commands = new Set<string>();
|
|
170
|
+
const hosts = new Set<string>();
|
|
171
|
+
let netMode: NetCapability['mode'] | undefined;
|
|
172
|
+
let subprocess = false;
|
|
173
|
+
let timeMs: number | undefined;
|
|
174
|
+
let memMb: number | undefined;
|
|
175
|
+
|
|
176
|
+
for (const spec of specs) {
|
|
177
|
+
if (!spec) continue;
|
|
178
|
+
for (const g of spec.fs?.read ?? []) read.add(g);
|
|
179
|
+
for (const g of spec.fs?.write ?? []) write.add(g);
|
|
180
|
+
if (spec.net) {
|
|
181
|
+
if (spec.net.mode === 'any') netMode = 'any';
|
|
182
|
+
else if (spec.net.mode === 'allowlist') {
|
|
183
|
+
if (netMode !== 'any') netMode = 'allowlist';
|
|
184
|
+
for (const h of spec.net.hosts) hosts.add(h);
|
|
185
|
+
} else netMode ??= 'none';
|
|
186
|
+
}
|
|
187
|
+
for (const e of spec.env ?? []) env.add(e);
|
|
188
|
+
if (spec.subprocess) subprocess = true;
|
|
189
|
+
for (const c of spec.commands ?? []) commands.add(c);
|
|
190
|
+
if (spec.timeMs !== undefined) timeMs = Math.max(timeMs ?? 0, spec.timeMs);
|
|
191
|
+
if (spec.memMb !== undefined) memMb = Math.max(memMb ?? 0, spec.memMb);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const sorted = (s: Set<string>): string[] => [...s].sort();
|
|
195
|
+
return {
|
|
196
|
+
...(read.size || write.size
|
|
197
|
+
? {
|
|
198
|
+
fs: {
|
|
199
|
+
...(read.size ? { read: sorted(read) } : {}),
|
|
200
|
+
...(write.size ? { write: sorted(write) } : {}),
|
|
201
|
+
},
|
|
202
|
+
}
|
|
203
|
+
: {}),
|
|
204
|
+
...(netMode === 'allowlist'
|
|
205
|
+
? { net: { mode: 'allowlist' as const, hosts: sorted(hosts) } }
|
|
206
|
+
: netMode
|
|
207
|
+
? { net: { mode: netMode } }
|
|
208
|
+
: {}),
|
|
209
|
+
...(env.size ? { env: sorted(env) } : {}),
|
|
210
|
+
...(timeMs !== undefined ? { timeMs } : {}),
|
|
211
|
+
...(memMb !== undefined ? { memMb } : {}),
|
|
212
|
+
...(subprocess ? { subprocess: true } : {}),
|
|
213
|
+
...(commands.size ? { commands: sorted(commands) } : {}),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { ModeContext } from '../mode.js';
|
|
2
|
+
import type { StopReason } from '../provider-utils.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Turn-end checkpoints — the extension point that lets a mode gate the moment
|
|
6
|
+
* the model stops calling tools and (apparently) finishes its turn. The shared
|
|
7
|
+
* ReAct loop ({@link runReactLoop}) invokes each checkpoint in declared order
|
|
8
|
+
* at that instant; a checkpoint can approve the completion (`pass`), feed a
|
|
9
|
+
* correction back into the conversation and keep the turn alive (`inject`),
|
|
10
|
+
* re-ask the model without new input (`retry`), or end the turn on its own
|
|
11
|
+
* terms (`stop`).
|
|
12
|
+
*
|
|
13
|
+
* A checkpoint's `run` may do anything async — run a fixed shell command via
|
|
14
|
+
* `ctx.tools.execute`, or spawn and await a full child agent turn via
|
|
15
|
+
* `ctx.subagents.spawn` (a lint gate, a completion reviewer). Two rules keep
|
|
16
|
+
* that power safe:
|
|
17
|
+
*
|
|
18
|
+
* 1. Honor `check.signal` in every await. It aborts on user cancel AND on
|
|
19
|
+
* this checkpoint's `timeoutMs` — an await that ignores it outlives the
|
|
20
|
+
* turn.
|
|
21
|
+
* 2. Never spawn a child running a checkpoint-bearing mode (pass
|
|
22
|
+
* `mode: 'default'`). The loop core also disarms checkpoints inside
|
|
23
|
+
* subagent sessions ({@link ModeContext.isSubagent}) as a backstop, so a
|
|
24
|
+
* mistake here degrades to an ungated child instead of unbounded
|
|
25
|
+
* recursion.
|
|
26
|
+
*/
|
|
27
|
+
export type CheckpointResult =
|
|
28
|
+
| { readonly action: 'pass' }
|
|
29
|
+
| {
|
|
30
|
+
readonly action: 'inject';
|
|
31
|
+
/**
|
|
32
|
+
* Feedback fed back to the model. Persistent by default: appended to the
|
|
33
|
+
* event log as a checkpoint-origin `user_prompt`, so it survives
|
|
34
|
+
* resume/replay and projects into every subsequent provider call.
|
|
35
|
+
* Clamped to the loop's `maxInjectBytes`.
|
|
36
|
+
*/
|
|
37
|
+
readonly text: string;
|
|
38
|
+
/**
|
|
39
|
+
* When true the text is NOT logged — it rides the next provider call as
|
|
40
|
+
* a volatile trailing user message (goal mode's nudge mechanism) and
|
|
41
|
+
* vanishes afterwards. Use for steering ("keep going"), not for
|
|
42
|
+
* substantive feedback the model must act on across several tool
|
|
43
|
+
* batches. The loop forwards `volatileTailCount` to the cache strategy
|
|
44
|
+
* so the rolling tail breakpoint stays ahead of it.
|
|
45
|
+
*/
|
|
46
|
+
readonly volatile?: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Loop again with no new input (idle-tolerant modes count idle rounds
|
|
50
|
+
* before giving up). Counts against the injection budget like `inject`.
|
|
51
|
+
*/
|
|
52
|
+
| { readonly action: 'retry' }
|
|
53
|
+
/**
|
|
54
|
+
* End the turn NOW, skipping any remaining checkpoints. The checkpoint is
|
|
55
|
+
* expected to have already emitted its own wrap-up events via `ctx.emit`
|
|
56
|
+
* (a stall notice, a completion summary) — the loop adds nothing.
|
|
57
|
+
*/
|
|
58
|
+
| { readonly action: 'stop' };
|
|
59
|
+
|
|
60
|
+
export interface CheckpointContext {
|
|
61
|
+
/** The final text the model produced for this turn-end candidate. */
|
|
62
|
+
readonly candidateText: string;
|
|
63
|
+
/**
|
|
64
|
+
* How the provider ended the candidate call. Checkpoints that verify
|
|
65
|
+
* completion claims should treat only `'end_turn'` as a claim — a
|
|
66
|
+
* `'max_tokens'` candidate is a truncation, not a claim.
|
|
67
|
+
*/
|
|
68
|
+
readonly stopReason: StopReason;
|
|
69
|
+
readonly iteration: number;
|
|
70
|
+
/**
|
|
71
|
+
* Consecutive turn-end candidates without an intervening tool batch,
|
|
72
|
+
* 1-based (this candidate included). Resets when the model does real work.
|
|
73
|
+
* Idle-tolerant modes use this as their stall counter.
|
|
74
|
+
*/
|
|
75
|
+
readonly consecutiveIdle: number;
|
|
76
|
+
/** `inject`/`retry` rounds already spent this turn. */
|
|
77
|
+
readonly injectionsUsed: number;
|
|
78
|
+
/** The loop's `maxInjections` — lets a checkpoint soften its bar on the last round. */
|
|
79
|
+
readonly injectionBudget: number;
|
|
80
|
+
/**
|
|
81
|
+
* Aborts on user cancel OR this checkpoint's timeout. Thread it into every
|
|
82
|
+
* await (`ctx.tools.execute(..., signal)`, subagent spawns, fetches).
|
|
83
|
+
*/
|
|
84
|
+
readonly signal: AbortSignal;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface TurnCheckpoint {
|
|
88
|
+
/** Stable name — stamped on checkpoint plugin events and injected-prompt origins. */
|
|
89
|
+
readonly name: string;
|
|
90
|
+
/**
|
|
91
|
+
* Wall-clock budget for one evaluation (default 120_000 ms, floored at
|
|
92
|
+
* 1_000). On timeout the checkpoint fails OPEN: the loop logs a visible
|
|
93
|
+
* warning and proceeds as if it passed — a stuck checker must never wedge
|
|
94
|
+
* the turn.
|
|
95
|
+
*/
|
|
96
|
+
readonly timeoutMs?: number;
|
|
97
|
+
/**
|
|
98
|
+
* Which turn-end candidates this checkpoint sees. `'end_turn'` (default)
|
|
99
|
+
* gates only natural completions — truncated/errored candidates bypass it.
|
|
100
|
+
* `'idle'` gates every no-tool completion regardless of stop reason
|
|
101
|
+
* (goal/collab-style stall handling wants these too).
|
|
102
|
+
*/
|
|
103
|
+
readonly gateOn?: 'end_turn' | 'idle';
|
|
104
|
+
run(check: CheckpointContext, ctx: ModeContext): Promise<CheckpointResult>;
|
|
105
|
+
}
|