@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.
Files changed (78) hide show
  1. package/dist/channel.d.ts +13 -0
  2. package/dist/channel.d.ts.map +1 -1
  3. package/dist/channel.js +19 -0
  4. package/dist/channel.js.map +1 -1
  5. package/dist/event-store.d.ts +5 -1
  6. package/dist/event-store.d.ts.map +1 -1
  7. package/dist/event-store.js +24 -1
  8. package/dist/event-store.js.map +1 -1
  9. package/dist/events.d.ts +9 -3
  10. package/dist/events.d.ts.map +1 -1
  11. package/dist/first-party.d.ts +17 -0
  12. package/dist/first-party.d.ts.map +1 -0
  13. package/dist/first-party.js +19 -0
  14. package/dist/first-party.js.map +1 -0
  15. package/dist/index.d.ts +9 -5
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +12 -5
  18. package/dist/index.js.map +1 -1
  19. package/dist/isolation.d.ts +14 -0
  20. package/dist/isolation.d.ts.map +1 -1
  21. package/dist/isolation.js +75 -0
  22. package/dist/isolation.js.map +1 -1
  23. package/dist/mode/checkpoint.d.ts +107 -0
  24. package/dist/mode/checkpoint.d.ts.map +1 -0
  25. package/dist/mode/checkpoint.js +2 -0
  26. package/dist/mode/checkpoint.js.map +1 -0
  27. package/dist/mode/react-loop.d.ts +129 -0
  28. package/dist/mode/react-loop.d.ts.map +1 -0
  29. package/dist/mode/react-loop.js +480 -0
  30. package/dist/mode/react-loop.js.map +1 -0
  31. package/dist/mode/stuck-loop.d.ts +7 -0
  32. package/dist/mode/stuck-loop.d.ts.map +1 -1
  33. package/dist/mode/stuck-loop.js +4 -0
  34. package/dist/mode/stuck-loop.js.map +1 -1
  35. package/dist/mode-helpers.d.ts +4 -0
  36. package/dist/mode-helpers.d.ts.map +1 -1
  37. package/dist/mode-helpers.js +3 -0
  38. package/dist/mode-helpers.js.map +1 -1
  39. package/dist/mode.d.ts +43 -2
  40. package/dist/mode.d.ts.map +1 -1
  41. package/dist/mode.js.map +1 -1
  42. package/dist/plugin.d.ts +10 -1
  43. package/dist/plugin.d.ts.map +1 -1
  44. package/dist/reflector.d.ts +58 -0
  45. package/dist/reflector.d.ts.map +1 -0
  46. package/dist/reflector.js +2 -0
  47. package/dist/reflector.js.map +1 -0
  48. package/dist/schemas.d.ts +215 -8
  49. package/dist/schemas.d.ts.map +1 -1
  50. package/dist/schemas.js +37 -0
  51. package/dist/schemas.js.map +1 -1
  52. package/dist/session-like.d.ts +52 -0
  53. package/dist/session-like.d.ts.map +1 -1
  54. package/dist/tool-dispatch.d.ts +35 -1
  55. package/dist/tool-dispatch.d.ts.map +1 -1
  56. package/dist/tool-dispatch.js +51 -0
  57. package/dist/tool-dispatch.js.map +1 -1
  58. package/package.json +1 -1
  59. package/src/channel.ts +25 -0
  60. package/src/event-store.ts +16 -1
  61. package/src/events.ts +9 -2
  62. package/src/exit-after-pair.test.ts +32 -0
  63. package/src/first-party.test.ts +26 -0
  64. package/src/first-party.ts +19 -0
  65. package/src/index.ts +32 -4
  66. package/src/isolation-aggregate.test.ts +69 -0
  67. package/src/isolation.ts +67 -0
  68. package/src/mode/checkpoint.ts +105 -0
  69. package/src/mode/react-loop.ts +694 -0
  70. package/src/mode/stuck-loop.ts +11 -0
  71. package/src/mode-helpers.ts +16 -0
  72. package/src/mode.ts +43 -2
  73. package/src/plugin.ts +10 -1
  74. package/src/reflector.ts +61 -0
  75. package/src/schemas.ts +41 -0
  76. package/src/session-like.ts +49 -0
  77. package/src/tool-dispatch.test.ts +77 -0
  78. 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
+ }