@moxxy/sdk 0.15.1 → 0.16.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 (53) hide show
  1. package/dist/compactor.d.ts +1 -1
  2. package/dist/compactor.d.ts.map +1 -1
  3. package/dist/index.d.ts +2 -2
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +5 -5
  6. package/dist/index.js.map +1 -1
  7. package/dist/mode/abort-backoff.d.ts +23 -0
  8. package/dist/mode/abort-backoff.d.ts.map +1 -0
  9. package/dist/mode/abort-backoff.js +53 -0
  10. package/dist/mode/abort-backoff.js.map +1 -0
  11. package/dist/mode/project-messages.d.ts.map +1 -1
  12. package/dist/mode/project-messages.js +38 -4
  13. package/dist/mode/project-messages.js.map +1 -1
  14. package/dist/mode-helpers.d.ts +1 -0
  15. package/dist/mode-helpers.d.ts.map +1 -1
  16. package/dist/mode-helpers.js +1 -0
  17. package/dist/mode-helpers.js.map +1 -1
  18. package/dist/plugin.d.ts +1 -1
  19. package/dist/requirements.d.ts +18 -3
  20. package/dist/requirements.d.ts.map +1 -1
  21. package/dist/server.d.ts +0 -1
  22. package/dist/server.d.ts.map +1 -1
  23. package/dist/server.js +0 -1
  24. package/dist/server.js.map +1 -1
  25. package/dist/tool-display.d.ts.map +1 -1
  26. package/dist/tool-display.js +23 -4
  27. package/dist/tool-display.js.map +1 -1
  28. package/dist/tunnel.d.ts +21 -34
  29. package/dist/tunnel.d.ts.map +1 -1
  30. package/dist/tunnel.js +11 -172
  31. package/dist/tunnel.js.map +1 -1
  32. package/dist/view-renderer.d.ts +6 -0
  33. package/dist/view-renderer.d.ts.map +1 -1
  34. package/dist/view-renderer.js +17 -3
  35. package/dist/view-renderer.js.map +1 -1
  36. package/package.json +1 -1
  37. package/src/compactor.ts +6 -1
  38. package/src/index.ts +7 -11
  39. package/src/loop-helpers.test.ts +72 -0
  40. package/src/mode/abort-backoff.test.ts +64 -0
  41. package/src/mode/abort-backoff.ts +53 -0
  42. package/src/mode/project-messages.ts +35 -4
  43. package/src/mode-helpers.ts +1 -0
  44. package/src/package-root.test.ts +0 -2
  45. package/src/plugin.ts +1 -1
  46. package/src/requirements.ts +15 -3
  47. package/src/server.ts +0 -1
  48. package/src/tool-display.test.ts +24 -0
  49. package/src/tool-display.ts +22 -6
  50. package/src/tunnel.ts +19 -198
  51. package/src/view-renderer.test.ts +12 -0
  52. package/src/view-renderer.ts +16 -2
  53. package/src/tunnel.test.ts +0 -99
package/dist/tunnel.js CHANGED
@@ -1,174 +1,13 @@
1
1
  /**
2
- * A swappable way to expose a locally-bound surface (the web channel) to the
3
- * user over the public internet — so an agent on Telegram/TUI can hand the user
4
- * a URL they can open. One provider is active per session (registered via
5
- * plugins, like every other block); core seeds a `localhost` no-op provider so
6
- * `getActive()` is non-null.
7
- */
8
- import { spawn } from 'node:child_process';
9
- // ---------------------------------------------------------------------------
10
- // Shared CLI-tunnel subprocess management
11
- //
12
- // cloudflared/ngrok (channel-web) and the webhooks tunnel all do the same
13
- // thing: spawn a CLI, watch its stdout/stderr for the assigned public URL,
14
- // resolve once it's seen (or reject on timeout/exit/error), and guarantee the
15
- // child is killed on close *and* on process exit (Node does not reap children).
16
- // `spawnCliTunnel` is the single implementation they all share.
17
- // ---------------------------------------------------------------------------
18
- /**
19
- * Guarantees spawned tunnel children (cloudflared/ngrok/…) never orphan or
20
- * leak. Node does NOT kill child processes when the parent exits, so we track
21
- * every live child and kill any survivors on process teardown — in addition to
22
- * the explicit `close()` path. Each tracked child is also force-killed
23
- * (SIGKILL) if it ignores SIGTERM, so a wedged tunnel can't drain
24
- * memory/handles.
25
- */
26
- const liveChildren = new Set();
27
- let exitHookInstalled = false;
28
- function killChild(child) {
29
- if (child.exitCode != null || child.signalCode != null)
30
- return;
31
- try {
32
- child.kill('SIGTERM');
33
- }
34
- catch {
35
- /* already gone */
36
- }
37
- // Escalate if it doesn't exit promptly. unref so this timer never holds the
38
- // event loop open on its own.
39
- const t = setTimeout(() => {
40
- try {
41
- child.kill('SIGKILL');
42
- }
43
- catch {
44
- /* gone */
45
- }
46
- }, 2000);
47
- t.unref?.();
48
- child.once('exit', () => clearTimeout(t));
49
- }
50
- function ensureExitHook() {
51
- if (exitHookInstalled)
52
- return;
53
- exitHookInstalled = true;
54
- const killAll = () => {
55
- for (const child of liveChildren) {
56
- try {
57
- child.kill('SIGKILL'); // process is exiting; be decisive, no async escalation
58
- }
59
- catch {
60
- /* gone */
61
- }
62
- }
63
- liveChildren.clear();
64
- };
65
- process.once('exit', killAll);
66
- // Registering a SIGINT/SIGTERM listener suppresses Node's default
67
- // terminate-on-signal behavior, so an entrypoint that spawns a tunnel but
68
- // installs no exit handler of its own would swallow the first Ctrl-C and hang
69
- // with the process still alive. Re-raise the signal after cleanup so the
70
- // default termination still fires. `process.once` removes the listener before
71
- // it runs, so re-raising hits Node's default disposition (terminate).
72
- const onSignal = (sig) => () => {
73
- killAll();
74
- process.kill(process.pid, sig);
75
- };
76
- process.once('SIGINT', onSignal('SIGINT'));
77
- process.once('SIGTERM', onSignal('SIGTERM'));
78
- }
79
- /** Track a spawned child; returns an `untrack()` that also kills it cleanly. */
80
- function trackChild(child) {
81
- ensureExitHook();
82
- liveChildren.add(child);
83
- child.once('exit', () => liveChildren.delete(child));
84
- return () => new Promise((resolve) => {
85
- liveChildren.delete(child);
86
- if (child.exitCode != null || child.signalCode != null)
87
- return resolve();
88
- child.once('exit', () => resolve());
89
- killChild(child);
90
- });
91
- }
92
- const DEFAULT_TUNNEL_URL_TIMEOUT_MS = 30_000;
93
- /**
94
- * Spawn a CLI tunnel, parse the assigned public URL out of its output, and
95
- * resolve a {@link CliTunnelHandle}. The child is tracked so it is killed on
96
- * `close()`, on tunnel-switch, and on process exit (no orphans). Rejects on
97
- * timeout, spawn error, or premature exit (the child is killed in every reject
98
- * path). This is the single spawn-and-parse implementation behind the
99
- * cloudflared/ngrok tunnel providers and the webhooks tunnel.
100
- */
101
- export function spawnCliTunnel(opts) {
102
- const { cmd, args, urlRegex } = opts;
103
- const name = opts.name ?? cmd;
104
- const timeoutMs = opts.timeoutMs ?? DEFAULT_TUNNEL_URL_TIMEOUT_MS;
105
- return new Promise((resolve, reject) => {
106
- let child;
107
- try {
108
- child = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'] });
109
- }
110
- catch (err) {
111
- reject(new Error(`failed to spawn ${name} — is it installed? (${err instanceof Error ? err.message : String(err)})`));
112
- return;
113
- }
114
- const untrack = trackChild(child);
115
- let settled = false;
116
- const timer = setTimeout(() => {
117
- if (settled)
118
- return;
119
- settled = true;
120
- void untrack();
121
- reject(new Error(`${name}: timed out after ${timeoutMs}ms waiting for the tunnel URL`));
122
- }, timeoutMs);
123
- timer.unref?.();
124
- // 'data' events are not line-buffered: the assigned URL can straddle two
125
- // chunk boundaries (large URL, fragmented flush). Match against a rolling
126
- // accumulation rather than each lone chunk, capped so a chatty tunnel that
127
- // never prints a URL can't grow this unboundedly while we wait.
128
- const MAX_BUF = 8192;
129
- let acc = '';
130
- const onData = (buf) => {
131
- if (settled)
132
- return; // drain quietly once resolved so the pipe never fills
133
- acc += buf.toString('utf8');
134
- if (acc.length > MAX_BUF)
135
- acc = acc.slice(acc.length - MAX_BUF);
136
- const url = urlRegex.exec(acc)?.[0] ?? null;
137
- if (!url)
138
- return;
139
- settled = true;
140
- acc = '';
141
- clearTimeout(timer);
142
- resolve({ url, pid: child.pid ?? -1, close: untrack });
143
- };
144
- child.stdout?.on('data', onData);
145
- child.stderr?.on('data', onData);
146
- child.on('error', (err) => {
147
- if (settled)
148
- return;
149
- settled = true;
150
- clearTimeout(timer);
151
- void untrack();
152
- reject(err);
153
- });
154
- child.on('exit', (code) => {
155
- if (settled)
156
- return;
157
- settled = true;
158
- clearTimeout(timer);
159
- reject(new Error(`${name} exited (code ${code ?? 'null'}) before emitting a URL`));
160
- });
161
- });
162
- }
163
- /**
164
- * Probe whether a tunnel CLI is installed and runnable (`<cmd> --version`
165
- * exits 0). Used as the `isAvailable()` gate by CLI-backed tunnel providers.
166
- */
167
- export function isCliTunnelAvailable(cmd) {
168
- return new Promise((resolve) => {
169
- const child = spawn(cmd, ['--version'], { stdio: 'ignore' });
170
- child.once('error', () => resolve(false));
171
- child.once('exit', (code) => resolve(code === 0));
172
- });
173
- }
2
+ * A swappable way to expose a locally-bound surface (the web channel, the mobile
3
+ * bridge, the webhooks listener) to the user over the public internet — so an
4
+ * agent on Telegram/TUI can hand the user a URL they can open. One provider is
5
+ * active per session (registered via plugins, like every other block); core
6
+ * seeds a `localhost` no-op provider so `getActive()` is non-null.
7
+ *
8
+ * The shipped provider is `@moxxy/plugin-tunnel-proxy` (the self-hosted proxy
9
+ * relay). The old cloudflared/ngrok subprocess providers — and the
10
+ * `spawnCliTunnel` helper that backed them — were removed.
11
+ */
12
+ export {};
174
13
  //# sourceMappingURL=tunnel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tunnel.js","sourceRoot":"","sources":["../src/tunnel.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tunnel.js","sourceRoot":"","sources":["../src/tunnel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
@@ -113,6 +113,12 @@ export declare const DEFAULT_VIEW_TAGS: ReadonlyArray<ViewTagSpec>;
113
113
  * Count element + text nodes in a view tree (the `nodeCount` a `present_view`
114
114
  * tool reports). Single source of truth: core's parser and the plugin-view tool
115
115
  * both import this rather than re-implementing the recursion.
116
+ *
117
+ * Iterative (explicit stack) rather than recursive: a deeply nested
118
+ * hand-built/corrupt AST would blow the call stack with a `RangeError`, and this
119
+ * runs on untrusted agent-authored input. Each node counts as 1; an element's
120
+ * children are pushed for later counting. Order-independent (we only sum), so
121
+ * the count is byte-identical to the old recursion for any normal tree.
116
122
  */
117
123
  export declare function countNodes(node: ViewNode): number;
118
124
  //# sourceMappingURL=view-renderer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"view-renderer.d.ts","sourceRoot":"","sources":["../src/view-renderer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"view-renderer.d.ts","sourceRoot":"","sources":["../src/view-renderer.ts"],"names":[],"mappings":"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"}
@@ -104,10 +104,24 @@ export const DEFAULT_VIEW_TAGS = [...VIEW_PRIMITIVES, ...VIEW_COMPONENTS];
104
104
  * Count element + text nodes in a view tree (the `nodeCount` a `present_view`
105
105
  * tool reports). Single source of truth: core's parser and the plugin-view tool
106
106
  * both import this rather than re-implementing the recursion.
107
+ *
108
+ * Iterative (explicit stack) rather than recursive: a deeply nested
109
+ * hand-built/corrupt AST would blow the call stack with a `RangeError`, and this
110
+ * runs on untrusted agent-authored input. Each node counts as 1; an element's
111
+ * children are pushed for later counting. Order-independent (we only sum), so
112
+ * the count is byte-identical to the old recursion for any normal tree.
107
113
  */
108
114
  export function countNodes(node) {
109
- if (node.kind === 'text')
110
- return 1;
111
- return 1 + node.children.reduce((sum, child) => sum + countNodes(child), 0);
115
+ let count = 0;
116
+ const stack = [node];
117
+ while (stack.length > 0) {
118
+ const current = stack.pop();
119
+ count += 1;
120
+ if (current.kind === 'element') {
121
+ for (const child of current.children)
122
+ stack.push(child);
123
+ }
124
+ }
125
+ return count;
112
126
  }
113
127
  //# sourceMappingURL=view-renderer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"view-renderer.js","sourceRoot":"","sources":["../src/view-renderer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"view-renderer.js","sourceRoot":"","sources":["../src/view-renderer.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moxxy/sdk",
3
- "version": "0.15.1",
3
+ "version": "0.16.0",
4
4
  "description": "Typed public surface for the moxxy framework: event types, define* factories, lifecycle hook signatures.",
5
5
  "keywords": [
6
6
  "moxxy",
package/src/compactor.ts CHANGED
@@ -26,5 +26,10 @@ export interface CompactContext {
26
26
  export interface CompactorDef {
27
27
  readonly name: string;
28
28
  shouldCompact(log: EventLogReader, budget: TokenBudget): boolean;
29
- compact(events: ReadonlyArray<MoxxyEvent>, ctx: CompactContext): Promise<Omit<CompactionEvent, keyof import('./events.js').EventBase> & { ts?: number }>;
29
+ // `ctx` is optional in the public contract: the dispatcher
30
+ // (`runCompactionIfNeeded`) always passes one, but impls treat it as optional
31
+ // (degrading gracefully when provider/model are absent), so a hand-rolled
32
+ // caller / test invoking `compact(events)` is valid and must not be a
33
+ // compile error.
34
+ compact(events: ReadonlyArray<MoxxyEvent>, ctx?: CompactContext): Promise<Omit<CompactionEvent, keyof import('./events.js').EventBase> & { ts?: number }>;
30
35
  }
package/src/index.ts CHANGED
@@ -175,17 +175,11 @@ export type {
175
175
  ViewRendererDef,
176
176
  } from './view-renderer.js';
177
177
  export { VIEW_PRIMITIVES, VIEW_COMPONENTS, DEFAULT_VIEW_TAGS, isSafeViewUrl, countNodes } from './view-renderer.js';
178
- export type {
179
- TunnelProviderDef,
180
- TunnelHandle,
181
- TunnelOpenOptions,
182
- SpawnCliTunnelOptions,
183
- CliTunnelHandle,
184
- } from './tunnel.js';
185
- // Node-runtime helpers (spawnCliTunnel/isCliTunnelAvailable, writeFileAtomic*,
186
- // moxxyHome/moxxyPath, readRequestBody/bearerTokenMatches, channel-auth) are
187
- // exported from the './server' subpath, NOT the main barrel — they statically
188
- // reach node:* builtins and would break a browser/RN bundle. See ./server.ts.
178
+ export type { TunnelProviderDef, TunnelHandle, TunnelOpenOptions } from './tunnel.js';
179
+ // Node-runtime helpers (writeFileAtomic*, moxxyHome/moxxyPath,
180
+ // readRequestBody/bearerTokenMatches, channel-auth) are exported from the
181
+ // './server' subpath, NOT the main barrel — they statically reach node:*
182
+ // builtins and would break a browser/RN bundle. See ./server.ts.
189
183
  export { isRetryableError, toFriendlyError, zodToJsonSchema, estimateTextTokens, type StopReason } from './provider-utils.js';
190
184
  export type { WriteFileAtomicOptions } from './fs-utils.js';
191
185
  export { createMutex, type Mutex } from './mutex.js';
@@ -226,6 +220,8 @@ export {
226
220
  buildSystemPromptWithSkills,
227
221
  createStuckLoopDetector,
228
222
  stableHash,
223
+ sleepWithAbort,
224
+ nextBackoffMs,
229
225
  type CollectedToolUse,
230
226
  type StreamResult,
231
227
  type ProjectMessagesOptions,
@@ -191,6 +191,78 @@ describe('projectMessagesFromLog', () => {
191
191
  expect(block.content).not.toContain('before');
192
192
  }
193
193
  });
194
+
195
+ it('projects an image-shaped tool result as an image block, not stringified text', () => {
196
+ // A screenshot/clipboard tool returns { mediaType, base64 }. The model must
197
+ // SEE the pixels (image block), not a giant base64 string JSON-stringified
198
+ // into the tool_result text.
199
+ const log = reader([
200
+ event(0, { type: 'user_prompt', turnId: t1, source: 'user', text: 'screenshot' }),
201
+ event(1, { type: 'tool_call_requested', turnId: t1, source: 'model', callId: 'c1', name: 'screenshot', input: {} }),
202
+ event(2, {
203
+ type: 'tool_result',
204
+ turnId: t1,
205
+ source: 'tool',
206
+ callId: 'c1',
207
+ ok: true,
208
+ output: { mediaType: 'image/png', base64: 'AAAABBBB' },
209
+ }),
210
+ ]);
211
+
212
+ const messages = projectMessagesFromLog({ log });
213
+ const toolResult = messages.find((m) => m.role === 'tool_result')!;
214
+ // tool_result block (satisfies the tool_use pairing) + the image block.
215
+ const imageBlock = toolResult.content.find((b) => b.type === 'image');
216
+ expect(imageBlock).toEqual({ type: 'image', mediaType: 'image/png', data: 'AAAABBBB' });
217
+ const resultBlock = toolResult.content.find((b) => b.type === 'tool_result');
218
+ expect(resultBlock).toMatchObject({ type: 'tool_result', toolUseId: 'c1' });
219
+ // The base64 bytes must NOT be stringified into the tool_result text.
220
+ if (resultBlock && resultBlock.type === 'tool_result') {
221
+ expect(resultBlock.content).not.toContain('AAAABBBB');
222
+ }
223
+ });
224
+
225
+ it('accepts the provider-native image shape { type:image, mediaType, data }', () => {
226
+ const log = reader([
227
+ event(0, { type: 'user_prompt', turnId: t1, source: 'user', text: 'grab' }),
228
+ event(1, { type: 'tool_call_requested', turnId: t1, source: 'model', callId: 'c1', name: 'grab', input: {} }),
229
+ event(2, {
230
+ type: 'tool_result',
231
+ turnId: t1,
232
+ source: 'tool',
233
+ callId: 'c1',
234
+ ok: true,
235
+ output: { type: 'image', mediaType: 'image/jpeg', data: 'ZZZZ' },
236
+ }),
237
+ ]);
238
+
239
+ const messages = projectMessagesFromLog({ log });
240
+ const toolResult = messages.find((m) => m.role === 'tool_result')!;
241
+ expect(toolResult.content.find((b) => b.type === 'image')).toEqual({
242
+ type: 'image',
243
+ mediaType: 'image/jpeg',
244
+ data: 'ZZZZ',
245
+ });
246
+ });
247
+
248
+ it('does not treat a failed/error tool result as an image', () => {
249
+ const log = reader([
250
+ event(0, { type: 'user_prompt', turnId: t1, source: 'user', text: 'grab' }),
251
+ event(1, { type: 'tool_call_requested', turnId: t1, source: 'model', callId: 'c1', name: 'grab', input: {} }),
252
+ event(2, {
253
+ type: 'tool_result',
254
+ turnId: t1,
255
+ source: 'tool',
256
+ callId: 'c1',
257
+ ok: false,
258
+ output: { mediaType: 'image/png', base64: 'AAAA' },
259
+ }),
260
+ ]);
261
+
262
+ const messages = projectMessagesFromLog({ log });
263
+ const toolResult = messages.find((m) => m.role === 'tool_result')!;
264
+ expect(toolResult.content.some((b) => b.type === 'image')).toBe(false);
265
+ });
194
266
  });
195
267
 
196
268
  function reader(events: ReadonlyArray<MoxxyEvent>): EventLogReader {
@@ -0,0 +1,64 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { nextBackoffMs, sleepWithAbort } from './abort-backoff.js';
3
+
4
+ describe('nextBackoffMs', () => {
5
+ it('grows exponentially from baseMs (1-based attempt)', () => {
6
+ expect(nextBackoffMs(1, 500)).toBe(500);
7
+ expect(nextBackoffMs(2, 500)).toBe(1000);
8
+ expect(nextBackoffMs(3, 500)).toBe(2000);
9
+ expect(nextBackoffMs(4, 500)).toBe(4000);
10
+ });
11
+
12
+ it('treats attempt <= 1 as baseMs', () => {
13
+ expect(nextBackoffMs(0, 500)).toBe(500);
14
+ expect(nextBackoffMs(-3, 500)).toBe(500);
15
+ });
16
+
17
+ it('caps at maxMs (default 30_000)', () => {
18
+ expect(nextBackoffMs(20, 500)).toBe(30_000);
19
+ expect(nextBackoffMs(20, 500, 5_000)).toBe(5_000);
20
+ });
21
+ });
22
+
23
+ describe('sleepWithAbort', () => {
24
+ it('resolves after the delay', async () => {
25
+ vi.useFakeTimers();
26
+ try {
27
+ const p = sleepWithAbort(1000);
28
+ vi.advanceTimersByTime(1000);
29
+ await expect(p).resolves.toBeUndefined();
30
+ } finally {
31
+ vi.useRealTimers();
32
+ }
33
+ });
34
+
35
+ it('rejects immediately when the signal is already aborted', async () => {
36
+ const ac = new AbortController();
37
+ ac.abort();
38
+ await expect(sleepWithAbort(1000, ac.signal)).rejects.toBeDefined();
39
+ });
40
+
41
+ it('rejects on abort mid-sleep and leaves no abort listener leaked', async () => {
42
+ const ac = new AbortController();
43
+ const add = vi.spyOn(ac.signal, 'addEventListener');
44
+ const p = sleepWithAbort(10_000, ac.signal);
45
+ ac.abort();
46
+ await expect(p).rejects.toBeDefined();
47
+ // The once-listener is consumed by the abort; nothing keeps it attached.
48
+ expect(add).toHaveBeenCalledTimes(1);
49
+ });
50
+
51
+ it('removes the abort listener when it resolves normally (no leak)', async () => {
52
+ vi.useFakeTimers();
53
+ try {
54
+ const ac = new AbortController();
55
+ const remove = vi.spyOn(ac.signal, 'removeEventListener');
56
+ const p = sleepWithAbort(50, ac.signal);
57
+ vi.advanceTimersByTime(50);
58
+ await p;
59
+ expect(remove).toHaveBeenCalledTimes(1);
60
+ } finally {
61
+ vi.useRealTimers();
62
+ }
63
+ });
64
+ });
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Shared retry primitives for loop strategies (mode-default / mode-goal).
3
+ *
4
+ * Both modes back off between retryable provider failures with an identical
5
+ * exponential schedule and an abort-aware sleep. The logic was duplicated in
6
+ * each mode's loop; these are the single source of truth they now import.
7
+ */
8
+
9
+ /**
10
+ * Sleep `ms` milliseconds, settling early if `signal` aborts. Rejects with the
11
+ * signal's abort reason (an `AbortError`-style `DOMException` when none) on
12
+ * abort so a back-off never silently outlives a cancelled turn, and crucially
13
+ * NEVER leaks the abort listener or the timer in any settle path (resolve,
14
+ * reject, or already-aborted) — a leaked listener on a long-lived signal
15
+ * accumulates across a turn's many retries.
16
+ */
17
+ export function sleepWithAbort(ms: number, signal?: AbortSignal): Promise<void> {
18
+ return new Promise<void>((resolve, reject) => {
19
+ if (signal?.aborted) {
20
+ reject(abortReason(signal));
21
+ return;
22
+ }
23
+ let onAbort: (() => void) | undefined;
24
+ const timer = setTimeout(() => {
25
+ if (signal && onAbort) signal.removeEventListener('abort', onAbort);
26
+ resolve();
27
+ }, Math.max(0, ms));
28
+ if (signal) {
29
+ onAbort = (): void => {
30
+ clearTimeout(timer);
31
+ reject(abortReason(signal));
32
+ };
33
+ signal.addEventListener('abort', onAbort, { once: true });
34
+ }
35
+ });
36
+ }
37
+
38
+ function abortReason(signal: AbortSignal): unknown {
39
+ // Prefer the caller-supplied reason; fall back to a standard AbortError.
40
+ const reason = (signal as { reason?: unknown }).reason;
41
+ if (reason !== undefined) return reason;
42
+ return new DOMException('The operation was aborted', 'AbortError');
43
+ }
44
+
45
+ /**
46
+ * Exponential back-off for a 1-based retry `attempt`: `baseMs * 2^(attempt-1)`,
47
+ * clamped to `[baseMs, maxMs]` (default cap 30_000ms). `attempt <= 1` yields
48
+ * `baseMs`. Matches the schedule both modes used before extraction.
49
+ */
50
+ export function nextBackoffMs(attempt: number, baseMs: number, maxMs = 30_000): number {
51
+ const exp = Math.max(0, Math.floor(attempt) - 1);
52
+ return Math.min(maxMs, baseMs * 2 ** exp);
53
+ }
@@ -35,6 +35,28 @@ function safeStringifyOutput(output: unknown): string {
35
35
  }
36
36
  }
37
37
 
38
+ /**
39
+ * An image ContentBlock extracted from an image-shaped tool output. A tool can
40
+ * return either `{ mediaType, base64 }` (the desktop screenshot/clipboard shape)
41
+ * or the provider-native `{ type:'image', mediaType, data }` — both normalize to
42
+ * the same provider `image` block so the model SEES the pixels instead of a
43
+ * stringified blob of base64.
44
+ */
45
+ function imageBlockFromOutput(output: unknown): Extract<ContentBlock, { type: 'image' }> | null {
46
+ if (typeof output !== 'object' || output === null) return null;
47
+ const o = output as { type?: unknown; mediaType?: unknown; base64?: unknown; data?: unknown };
48
+ if (typeof o.mediaType !== 'string') return null;
49
+ // `{ mediaType, base64 }` (raw shape) or `{ type:'image', mediaType, data }`.
50
+ const data =
51
+ typeof o.base64 === 'string'
52
+ ? o.base64
53
+ : o.type === 'image' && typeof o.data === 'string'
54
+ ? o.data
55
+ : null;
56
+ if (data === null) return null;
57
+ return { type: 'image', mediaType: o.mediaType, data };
58
+ }
59
+
38
60
  /** Appended to the system prompt while elision is active (see projection). */
39
61
  export const ELISION_SYSTEM_NOTE =
40
62
  'Context note: to stay within budget, older turns may appear as stubs like ' +
@@ -461,6 +483,10 @@ export function projectMessages(
461
483
  // Stub bulky old tool output to a recall-able marker (decision shared
462
484
  // with estimateContextTokens via toolResultStubbed).
463
485
  let text: string;
486
+ // An image-shaped output (a screenshot/clipboard grab) is carried as a
487
+ // provider `image` block so the model SEES the pixels — only when the
488
+ // result isn't stubbed (elided) or an error and the call succeeded.
489
+ let image: Extract<ContentBlock, { type: 'image' }> | null = null;
464
490
  if (toolResultStubbed(e, el)) {
465
491
  const recalled = el.recalledCallIds.has(e.callId) || el.recalledSeqs.has(e.seq);
466
492
  text = toolResultStub(e.callId, toolResultBytes(e.output), recalled);
@@ -470,13 +496,18 @@ export function projectMessages(
470
496
  // Rich result (e.g. a file diff): the model only needs the short
471
497
  // `forModel` summary — the structured `display` is for channels.
472
498
  text = e.output.forModel;
499
+ } else if (e.ok && (image = imageBlockFromOutput(e.output))) {
500
+ // Short marker satisfies the tool_use→tool_result pairing; the image
501
+ // block (appended below) carries the actual pixels.
502
+ text = '[image returned by tool — see attached image]';
473
503
  } else {
474
504
  text = safeStringifyOutput(e.output);
475
505
  }
476
- messages.push({
477
- role: 'tool_result',
478
- content: [{ type: 'tool_result', toolUseId: e.callId, content: text, isError: !e.ok }],
479
- });
506
+ const content: ContentBlock[] = [
507
+ { type: 'tool_result', toolUseId: e.callId, content: text, isError: !e.ok },
508
+ ];
509
+ if (image) content.push(image);
510
+ messages.push({ role: 'tool_result', content });
480
511
  recordStable(e.seq);
481
512
  break;
482
513
  }
@@ -25,6 +25,7 @@ export {
25
25
  type StreamResult,
26
26
  } from './mode/collect-stream.js';
27
27
  export { runSingleShotTurn } from './mode/single-shot.js';
28
+ export { sleepWithAbort, nextBackoffMs } from './mode/abort-backoff.js';
28
29
  export {
29
30
  createStuckLoopDetector,
30
31
  type StuckLoopDetector,
@@ -44,8 +44,6 @@ describe('@moxxy/sdk package root', () => {
44
44
  // re-export on the main barrel is caught (see .dependency-cruiser.cjs
45
45
  // `no-node-builtins-in-renderer`).
46
46
  const NODE_ONLY_VALUE_EXPORTS = [
47
- 'spawnCliTunnel',
48
- 'isCliTunnelAvailable',
49
47
  'writeFileAtomic',
50
48
  'writeFileAtomicSync',
51
49
  'moxxyHome',
package/src/plugin.ts CHANGED
@@ -41,7 +41,7 @@ export interface PluginSpec {
41
41
  readonly viewRenderers?: ReadonlyArray<ViewRendererDef>;
42
42
  /**
43
43
  * Tunnel providers that expose the local web surface publicly (e.g.
44
- * cloudflared). One active per session; core seeds a `localhost` no-op.
44
+ * the proxy relay). One active per session; core seeds a `localhost` no-op.
45
45
  */
46
46
  readonly tunnelProviders?: ReadonlyArray<TunnelProviderDef>;
47
47
  readonly channels?: ReadonlyArray<ChannelDef>;