@moxxy/sdk 0.5.1 → 0.7.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/README.md +3 -3
- package/dist/channel-auth.d.ts +37 -0
- package/dist/channel-auth.d.ts.map +1 -0
- package/dist/channel-auth.js +58 -0
- package/dist/channel-auth.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/tool-dispatch.d.ts +50 -3
- package/dist/tool-dispatch.d.ts.map +1 -1
- package/dist/tool-dispatch.js +100 -1
- package/dist/tool-dispatch.js.map +1 -1
- package/package.json +1 -1
- package/src/channel-auth.ts +68 -0
- package/src/index.ts +11 -1
- package/src/tool-dispatch.ts +136 -3
package/README.md
CHANGED
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
<a href="https://www.typescriptlang.org">
|
|
22
22
|
<img src="https://img.shields.io/badge/TypeScript-strict-3178C6?logo=typescript&logoColor=white" alt="TypeScript" />
|
|
23
23
|
</a>
|
|
24
|
-
<a href="https://github.com/moxxy-ai/
|
|
25
|
-
<img src="https://github.com/moxxy-ai/
|
|
24
|
+
<a href="https://github.com/moxxy-ai/moxxy/actions/workflows/ci.yml">
|
|
25
|
+
<img src="https://github.com/moxxy-ai/moxxy/actions/workflows/ci.yml/badge.svg" alt="CI" />
|
|
26
26
|
</a>
|
|
27
27
|
</p>
|
|
28
28
|
|
|
@@ -220,7 +220,7 @@ export default definePlugin({
|
|
|
220
220
|
});
|
|
221
221
|
```
|
|
222
222
|
|
|
223
|
-
Per-block author guides for skill, plugin, tool, channel, provider, loop strategy, compactor, and cache strategy live in the monorepo at [`.claude/agents/`](https://github.com/moxxy-ai/
|
|
223
|
+
Per-block author guides for skill, plugin, tool, channel, provider, loop strategy, compactor, and cache strategy live in the monorepo at [`.claude/agents/`](https://github.com/moxxy-ai/moxxy/tree/main/.claude/agents).
|
|
224
224
|
|
|
225
225
|
## Architectural rules
|
|
226
226
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard auth toolkit for channels — so a new channel gets consistent
|
|
3
|
+
* connection auth without re-rolling token handling. Two pieces:
|
|
4
|
+
*
|
|
5
|
+
* - {@link resolveChannelToken} — the standard `channels.<name>` token
|
|
6
|
+
* resolution (env → config → a generated, persisted secret). Never empty.
|
|
7
|
+
* - {@link bearerGuard} — a pre-connection handler: a constant-time bearer
|
|
8
|
+
* check a channel runs at its accept point (a WebSocket handshake, an HTTP
|
|
9
|
+
* request) before admitting a client.
|
|
10
|
+
*
|
|
11
|
+
* Channels that expose a network surface (mobile WS bridge, HTTP, web) should
|
|
12
|
+
* resolve their token with the former and gate connections with the latter,
|
|
13
|
+
* so the `channels.<name>.token` config + `MOXXY_<NAME>_TOKEN` env conventions
|
|
14
|
+
* are uniform across every channel.
|
|
15
|
+
*/
|
|
16
|
+
export interface ChannelTokenOptions {
|
|
17
|
+
/** Explicit token from channel config (`channels.<name>.token`). */
|
|
18
|
+
readonly configured?: string;
|
|
19
|
+
/** Env var checked first, e.g. `MOXXY_MOBILE_TOKEN`. */
|
|
20
|
+
readonly envVar?: string;
|
|
21
|
+
/** File (under the moxxy home dir) to persist a generated token, e.g. `mobile-token`. */
|
|
22
|
+
readonly fileName: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve a channel's auth token. Precedence: `envVar` → `configured` → a
|
|
26
|
+
* 256-bit secret generated once and persisted (0600) under the moxxy home, so
|
|
27
|
+
* the same pairing secret survives restarts. Never returns an empty string —
|
|
28
|
+
* a network-reachable channel must always be authenticated.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveChannelToken(opts: ChannelTokenOptions): string;
|
|
31
|
+
/**
|
|
32
|
+
* A pre-connection auth handler: returns a guard that constant-time-compares a
|
|
33
|
+
* presented bearer token against `expected`. An empty `expected` always denies
|
|
34
|
+
* (never accidentally run an open surface).
|
|
35
|
+
*/
|
|
36
|
+
export declare function bearerGuard(expected: string): (presented: string | undefined | null) => boolean;
|
|
37
|
+
//# sourceMappingURL=channel-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-auth.d.ts","sourceRoot":"","sources":["../src/channel-auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AASH,MAAM,WAAW,mBAAmB;IAClC,oEAAoE;IACpE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,wDAAwD;IACxD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,yFAAyF;IACzF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAMD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,mBAAmB,GAAG,MAAM,CAgBrE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,KAAK,OAAO,CAE/F"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard auth toolkit for channels — so a new channel gets consistent
|
|
3
|
+
* connection auth without re-rolling token handling. Two pieces:
|
|
4
|
+
*
|
|
5
|
+
* - {@link resolveChannelToken} — the standard `channels.<name>` token
|
|
6
|
+
* resolution (env → config → a generated, persisted secret). Never empty.
|
|
7
|
+
* - {@link bearerGuard} — a pre-connection handler: a constant-time bearer
|
|
8
|
+
* check a channel runs at its accept point (a WebSocket handshake, an HTTP
|
|
9
|
+
* request) before admitting a client.
|
|
10
|
+
*
|
|
11
|
+
* Channels that expose a network surface (mobile WS bridge, HTTP, web) should
|
|
12
|
+
* resolve their token with the former and gate connections with the latter,
|
|
13
|
+
* so the `channels.<name>.token` config + `MOXXY_<NAME>_TOKEN` env conventions
|
|
14
|
+
* are uniform across every channel.
|
|
15
|
+
*/
|
|
16
|
+
import { randomBytes } from 'node:crypto';
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import os from 'node:os';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { bearerTokenMatches } from './http-utils.js';
|
|
21
|
+
function moxxyHome() {
|
|
22
|
+
return process.env.MOXXY_HOME?.trim() || path.join(os.homedir(), '.moxxy');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve a channel's auth token. Precedence: `envVar` → `configured` → a
|
|
26
|
+
* 256-bit secret generated once and persisted (0600) under the moxxy home, so
|
|
27
|
+
* the same pairing secret survives restarts. Never returns an empty string —
|
|
28
|
+
* a network-reachable channel must always be authenticated.
|
|
29
|
+
*/
|
|
30
|
+
export function resolveChannelToken(opts) {
|
|
31
|
+
const fromEnv = (opts.envVar ? process.env[opts.envVar] : undefined)?.trim();
|
|
32
|
+
if (fromEnv)
|
|
33
|
+
return fromEnv;
|
|
34
|
+
if (opts.configured && opts.configured.trim())
|
|
35
|
+
return opts.configured.trim();
|
|
36
|
+
const file = path.join(moxxyHome(), opts.fileName);
|
|
37
|
+
try {
|
|
38
|
+
const existing = fs.readFileSync(file, 'utf8').trim();
|
|
39
|
+
if (existing)
|
|
40
|
+
return existing;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// not yet created
|
|
44
|
+
}
|
|
45
|
+
const token = randomBytes(32).toString('hex');
|
|
46
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
47
|
+
fs.writeFileSync(file, token, { mode: 0o600 });
|
|
48
|
+
return token;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A pre-connection auth handler: returns a guard that constant-time-compares a
|
|
52
|
+
* presented bearer token against `expected`. An empty `expected` always denies
|
|
53
|
+
* (never accidentally run an open surface).
|
|
54
|
+
*/
|
|
55
|
+
export function bearerGuard(expected) {
|
|
56
|
+
return (presented) => (expected ? bearerTokenMatches(presented, expected) : false);
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=channel-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-auth.js","sourceRoot":"","sources":["../src/channel-auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAWrD,SAAS,SAAS;IAChB,OAAO,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;AAC7E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAyB;IAC3D,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC;IAC7E,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAC5B,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;IAE7E,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACnD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QACtD,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,kBAAkB;IACpB,CAAC;IACD,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9C,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/C,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,QAAgB;IAC1C,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,kBAAkB,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;AACrF,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -18,10 +18,11 @@ export { isRetryableError, toFriendlyError, zodToJsonSchema, estimateTextTokens,
|
|
|
18
18
|
export { writeFileAtomic, moxxyHome, moxxyPath, type WriteFileAtomicOptions } from './fs-utils.js';
|
|
19
19
|
export { createMutex, type Mutex } from './mutex.js';
|
|
20
20
|
export { readRequestBody, bearerTokenMatches } from './http-utils.js';
|
|
21
|
+
export { resolveChannelToken, bearerGuard, type ChannelTokenOptions, } from './channel-auth.js';
|
|
21
22
|
export { autoAllowResolver, denyByDefaultResolver, createCallbackResolver, createAllowListResolver, createDeferredPermissionResolver, evaluateToolRule, type CallbackResolverOptions, type PermissionPromptHandler, type DeferredPermissionResolver, type DeferredPermissionResolverOptions, } from './resolvers.js';
|
|
22
23
|
export { MoxxyError, classifyHttpStatus, classifyNetworkError, type MoxxyErrorCode, type MoxxyErrorInit, } from './errors.js';
|
|
23
24
|
export { collectProviderStream, runSingleShotTurn, projectMessagesFromLog, projectMessages, buildSystemPromptWithSkills, createStuckLoopDetector, stableHash, type CollectedToolUse, type StreamResult, type ProjectMessagesOptions, type ProjectedMessages, type StuckLoopDetector, type StuckSignal, } from './mode-helpers.js';
|
|
24
|
-
export { dispatchToolCall } from './tool-dispatch.js';
|
|
25
|
+
export { dispatchToolCall, executeToolUses, emitRequestsAndDetectStuck, type StuckLoopReport, } from './tool-dispatch.js';
|
|
25
26
|
export type { TokenBudget, CompactContext, CompactorDef } from './compactor.js';
|
|
26
27
|
export { estimateContextTokens, runCompactionIfNeeded, isContextOverflowError, } from './compactor-helpers.js';
|
|
27
28
|
export { runElisionIfNeeded, resolveElisionSettings, type ResolvedElisionSettings, } from './elision-helpers.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -5,10 +5,11 @@ export { isRetryableError, toFriendlyError, zodToJsonSchema, estimateTextTokens
|
|
|
5
5
|
export { writeFileAtomic, moxxyHome, moxxyPath } from './fs-utils.js';
|
|
6
6
|
export { createMutex } from './mutex.js';
|
|
7
7
|
export { readRequestBody, bearerTokenMatches } from './http-utils.js';
|
|
8
|
+
export { resolveChannelToken, bearerGuard, } from './channel-auth.js';
|
|
8
9
|
export { autoAllowResolver, denyByDefaultResolver, createCallbackResolver, createAllowListResolver, createDeferredPermissionResolver, evaluateToolRule, } from './resolvers.js';
|
|
9
10
|
export { MoxxyError, classifyHttpStatus, classifyNetworkError, } from './errors.js';
|
|
10
11
|
export { collectProviderStream, runSingleShotTurn, projectMessagesFromLog, projectMessages, buildSystemPromptWithSkills, createStuckLoopDetector, stableHash, } from './mode-helpers.js';
|
|
11
|
-
export { dispatchToolCall } from './tool-dispatch.js';
|
|
12
|
+
export { dispatchToolCall, executeToolUses, emitRequestsAndDetectStuck, } from './tool-dispatch.js';
|
|
12
13
|
export { estimateContextTokens, runCompactionIfNeeded, isContextOverflowError, } from './compactor-helpers.js';
|
|
13
14
|
export { runElisionIfNeeded, resolveElisionSettings, } from './elision-helpers.js';
|
|
14
15
|
export { computeElisionState, toolResultStub, conversationalStub, toolResultBytes, toolResultStubbed, conversationalStubbed, TINY_TURN_CHARS, } from './elision-state.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAmGjG,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAsChD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEzF,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,eAAe,EAAE,kBAAkB,EAAmB,MAAM,qBAAqB,CAAC;AAC9H,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,SAAS,EAA+B,MAAM,eAAe,CAAC;AACnG,OAAO,EAAE,WAAW,EAAc,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,EACvB,gCAAgC,EAChC,gBAAgB,GAKjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,oBAAoB,GAGrB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,sBAAsB,EACtB,eAAe,EACf,2BAA2B,EAC3B,uBAAuB,EACvB,UAAU,GAOX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAmGjG,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAsChD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEzF,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,eAAe,EAAE,kBAAkB,EAAmB,MAAM,qBAAqB,CAAC;AAC9H,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,SAAS,EAA+B,MAAM,eAAe,CAAC;AACnG,OAAO,EAAE,WAAW,EAAc,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,EACL,mBAAmB,EACnB,WAAW,GAEZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,EACvB,gCAAgC,EAChC,gBAAgB,GAKjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,oBAAoB,GAGrB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,sBAAsB,EACtB,eAAe,EACf,2BAA2B,EAC3B,uBAAuB,EACvB,UAAU,GAOX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,0BAA0B,GAE3B,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,kBAAkB,EAClB,sBAAsB,GAEvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,GAEhB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,cAAc,EACd,cAAc,EACd,eAAe,EACf,eAAe,GAEhB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,sBAAsB,EACtB,gCAAgC,EAChC,sBAAsB,EACtB,cAAc,EACd,gBAAgB,GAGjB,MAAM,uBAAuB,CAAC;AA4E/B,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AA8B/D,OAAO,EACL,YAAY,EACZ,UAAU,EACV,cAAc,EACd,UAAU,EACV,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,EACpB,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,WAAW,EACX,sBAAsB,GACvB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAE5C,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,GAIlB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,cAAc,GAGf,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,qBAAqB,EACrB,6BAA6B,EAC7B,kBAAkB,GACnB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC"}
|
package/dist/tool-dispatch.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { MoxxyEvent } from './events.js';
|
|
1
|
+
import type { EmittedEvent, MoxxyEvent } from './events.js';
|
|
2
2
|
import type { ModeContext } from './mode.js';
|
|
3
|
-
import type { CollectedToolUse } from './mode-helpers.js';
|
|
3
|
+
import type { CollectedToolUse, StuckLoopDetector, StuckSignal } from './mode-helpers.js';
|
|
4
4
|
/**
|
|
5
5
|
* Execute a single tool-use end-to-end: dispatch `dispatchToolCall` hooks, run
|
|
6
6
|
* the permission check, invoke the tool, and emit the approved/denied/result
|
|
@@ -8,7 +8,7 @@ import type { CollectedToolUse } from './mode-helpers.js';
|
|
|
8
8
|
* don't need the events can drain it (`for await (const _ of …) {}`) — either
|
|
9
9
|
* way the events reach the log via `ctx.emit`.
|
|
10
10
|
*
|
|
11
|
-
* Shared by every loop strategy (
|
|
11
|
+
* Shared by every loop strategy (default, goal, and any contributed mode) so
|
|
12
12
|
* the defensive outer try/catch below lives in ONE place. Without it, a throw
|
|
13
13
|
* from a hook handler / permission resolver / the emit itself escapes the
|
|
14
14
|
* generator and leaves this (and any later) call as an orphan
|
|
@@ -16,4 +16,51 @@ import type { CollectedToolUse } from './mode-helpers.js';
|
|
|
16
16
|
* then rejects on the next turn.
|
|
17
17
|
*/
|
|
18
18
|
export declare function dispatchToolCall(ctx: ModeContext, t: CollectedToolUse, iteration: number): AsyncGenerator<MoxxyEvent, void, unknown>;
|
|
19
|
+
/**
|
|
20
|
+
* Run a batch of tool uses in order, handling mid-batch abort. On
|
|
21
|
+
* `ctx.signal.aborted` it synthesizes a failed `tool_result` for every
|
|
22
|
+
* not-yet-run call (so the log never ends on an orphan `tool_call_requested`),
|
|
23
|
+
* emits an `abort`, and returns `true` to tell the caller to stop. Returns
|
|
24
|
+
* `false` after a clean batch. Shared by every loop strategy so the
|
|
25
|
+
* orphan-on-abort guarantee lives in one place.
|
|
26
|
+
*/
|
|
27
|
+
export declare function executeToolUses(ctx: ModeContext, toolUses: ReadonlyArray<CollectedToolUse>, iteration: number): AsyncGenerator<MoxxyEvent, boolean, unknown>;
|
|
28
|
+
/**
|
|
29
|
+
* How a loop strategy phrases its stuck-loop abort. The skeleton — emit a
|
|
30
|
+
* `tool_call_requested` per call, feed the detector, and on a trip synthesize a
|
|
31
|
+
* failed `tool_result` for every already-emitted call before a fatal error — is
|
|
32
|
+
* identical across strategies and lives in {@link emitRequestsAndDetectStuck};
|
|
33
|
+
* only the wording, and goal mode's extra `goal_stuck` plugin_event, vary.
|
|
34
|
+
*/
|
|
35
|
+
export interface StuckLoopReport {
|
|
36
|
+
/** Message on the synthesized failed `tool_result` for the calls emitted
|
|
37
|
+
* before the trip (none of them ran). */
|
|
38
|
+
readonly abortedResultMessage: string;
|
|
39
|
+
/** Explanation for a `near` match (an `exact` match always reads
|
|
40
|
+
* "with identical input"). */
|
|
41
|
+
readonly nearHint: string;
|
|
42
|
+
/** Build the fatal error message shown to the user. */
|
|
43
|
+
readonly fatalMessage: (info: {
|
|
44
|
+
toolName: string;
|
|
45
|
+
count: number;
|
|
46
|
+
how: string;
|
|
47
|
+
}) => string;
|
|
48
|
+
/** Extra events to emit right before the fatal error — goal mode surfaces a
|
|
49
|
+
* `goal_stuck` plugin_event here. */
|
|
50
|
+
readonly extraOnStuck?: (info: {
|
|
51
|
+
toolName: string;
|
|
52
|
+
count: number;
|
|
53
|
+
kind: StuckSignal['kind'];
|
|
54
|
+
}) => ReadonlyArray<EmittedEvent>;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Emit a `tool_call_requested` for each tool use and feed it to the stuck-loop
|
|
58
|
+
* detector. Returns `true` when the detector trips (caller should stop) — after
|
|
59
|
+
* synthesizing a failed `tool_result` for every request emitted this batch.
|
|
60
|
+
* That synthesis is load-bearing: a stuck trip bails WITHOUT running
|
|
61
|
+
* {@link executeToolUses}, so without it the emitted requests are left as orphan
|
|
62
|
+
* `tool_call_requested` events (which render as a tool stuck "running" forever
|
|
63
|
+
* and which the provider rejects next turn). Returns `false` when no trip fired.
|
|
64
|
+
*/
|
|
65
|
+
export declare function emitRequestsAndDetectStuck(ctx: ModeContext, toolUses: ReadonlyArray<CollectedToolUse>, detector: StuckLoopDetector, report: StuckLoopReport): AsyncGenerator<MoxxyEvent, boolean, unknown>;
|
|
19
66
|
//# sourceMappingURL=tool-dispatch.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-dispatch.d.ts","sourceRoot":"","sources":["../src/tool-dispatch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"tool-dispatch.d.ts","sourceRoot":"","sources":["../src/tool-dispatch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE1F;;;;;;;;;;;;;GAaG;AACH,wBAAuB,gBAAgB,CACrC,GAAG,EAAE,WAAW,EAChB,CAAC,EAAE,gBAAgB,EACnB,SAAS,EAAE,MAAM,GAChB,cAAc,CAAC,UAAU,EAAE,IAAI,EAAE,OAAO,CAAC,CAkF3C;AAgCD;;;;;;;GAOG;AACH,wBAAuB,eAAe,CACpC,GAAG,EAAE,WAAW,EAChB,QAAQ,EAAE,aAAa,CAAC,gBAAgB,CAAC,EACzC,SAAS,EAAE,MAAM,GAChB,cAAc,CAAC,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,CAmC9C;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B;8CAC0C;IAC1C,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC;mCAC+B;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,uDAAuD;IACvD,QAAQ,CAAC,YAAY,EAAE,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAC;IAC1F;0CACsC;IACtC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE;QAC7B,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;KAC3B,KAAK,aAAa,CAAC,YAAY,CAAC,CAAC;CACnC;AAED;;;;;;;;GAQG;AACH,wBAAuB,0BAA0B,CAC/C,GAAG,EAAE,WAAW,EAChB,QAAQ,EAAE,aAAa,CAAC,gBAAgB,CAAC,EACzC,QAAQ,EAAE,iBAAiB,EAC3B,MAAM,EAAE,eAAe,GACtB,cAAc,CAAC,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,CA2C9C"}
|
package/dist/tool-dispatch.js
CHANGED
|
@@ -6,7 +6,7 @@ import { asToolCallId } from './ids.js';
|
|
|
6
6
|
* don't need the events can drain it (`for await (const _ of …) {}`) — either
|
|
7
7
|
* way the events reach the log via `ctx.emit`.
|
|
8
8
|
*
|
|
9
|
-
* Shared by every loop strategy (
|
|
9
|
+
* Shared by every loop strategy (default, goal, and any contributed mode) so
|
|
10
10
|
* the defensive outer try/catch below lives in ONE place. Without it, a throw
|
|
11
11
|
* from a hook handler / permission resolver / the emit itself escapes the
|
|
12
12
|
* generator and leaves this (and any later) call as an orphan
|
|
@@ -115,4 +115,103 @@ async function* emitDenied(ctx, t, reason, by) {
|
|
|
115
115
|
error: { kind: 'denied', message: reason },
|
|
116
116
|
});
|
|
117
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Run a batch of tool uses in order, handling mid-batch abort. On
|
|
120
|
+
* `ctx.signal.aborted` it synthesizes a failed `tool_result` for every
|
|
121
|
+
* not-yet-run call (so the log never ends on an orphan `tool_call_requested`),
|
|
122
|
+
* emits an `abort`, and returns `true` to tell the caller to stop. Returns
|
|
123
|
+
* `false` after a clean batch. Shared by every loop strategy so the
|
|
124
|
+
* orphan-on-abort guarantee lives in one place.
|
|
125
|
+
*/
|
|
126
|
+
export async function* executeToolUses(ctx, toolUses, iteration) {
|
|
127
|
+
// Tracks tool_call_requested events that haven't yet emitted a paired
|
|
128
|
+
// tool_result. On any early-exit (abort) we synthesize results for the
|
|
129
|
+
// leftovers so the event log can't end with orphan tool_call_requested events.
|
|
130
|
+
const unresolved = new Set(toolUses.map((t) => t.id));
|
|
131
|
+
for (const t of toolUses) {
|
|
132
|
+
if (ctx.signal.aborted) {
|
|
133
|
+
for (const orphanId of unresolved) {
|
|
134
|
+
yield await ctx.emit({
|
|
135
|
+
type: 'tool_result',
|
|
136
|
+
sessionId: ctx.sessionId,
|
|
137
|
+
turnId: ctx.turnId,
|
|
138
|
+
source: 'tool',
|
|
139
|
+
callId: asToolCallId(orphanId),
|
|
140
|
+
ok: false,
|
|
141
|
+
error: { kind: 'aborted', message: 'turn aborted before tool ran' },
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
unresolved.clear();
|
|
145
|
+
yield await ctx.emit({
|
|
146
|
+
type: 'abort',
|
|
147
|
+
sessionId: ctx.sessionId,
|
|
148
|
+
turnId: ctx.turnId,
|
|
149
|
+
source: 'system',
|
|
150
|
+
reason: 'signal aborted during tool execution',
|
|
151
|
+
});
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
yield* dispatchToolCall(ctx, t, iteration);
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
unresolved.delete(t.id);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Emit a `tool_call_requested` for each tool use and feed it to the stuck-loop
|
|
165
|
+
* detector. Returns `true` when the detector trips (caller should stop) — after
|
|
166
|
+
* synthesizing a failed `tool_result` for every request emitted this batch.
|
|
167
|
+
* That synthesis is load-bearing: a stuck trip bails WITHOUT running
|
|
168
|
+
* {@link executeToolUses}, so without it the emitted requests are left as orphan
|
|
169
|
+
* `tool_call_requested` events (which render as a tool stuck "running" forever
|
|
170
|
+
* and which the provider rejects next turn). Returns `false` when no trip fired.
|
|
171
|
+
*/
|
|
172
|
+
export async function* emitRequestsAndDetectStuck(ctx, toolUses, detector, report) {
|
|
173
|
+
const emitted = [];
|
|
174
|
+
for (const t of toolUses) {
|
|
175
|
+
yield await ctx.emit({
|
|
176
|
+
type: 'tool_call_requested',
|
|
177
|
+
sessionId: ctx.sessionId,
|
|
178
|
+
turnId: ctx.turnId,
|
|
179
|
+
source: 'model',
|
|
180
|
+
callId: asToolCallId(t.id),
|
|
181
|
+
name: t.name,
|
|
182
|
+
input: t.input,
|
|
183
|
+
});
|
|
184
|
+
emitted.push(t);
|
|
185
|
+
const sig = detector.record(t.name, t.input);
|
|
186
|
+
if (!sig.stuck)
|
|
187
|
+
continue;
|
|
188
|
+
for (const r of emitted) {
|
|
189
|
+
yield await ctx.emit({
|
|
190
|
+
type: 'tool_result',
|
|
191
|
+
sessionId: ctx.sessionId,
|
|
192
|
+
turnId: ctx.turnId,
|
|
193
|
+
source: 'tool',
|
|
194
|
+
callId: asToolCallId(r.id),
|
|
195
|
+
ok: false,
|
|
196
|
+
error: { kind: 'aborted', message: report.abortedResultMessage },
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
const how = sig.kind === 'near' ? report.nearHint : 'with identical input';
|
|
200
|
+
if (report.extraOnStuck) {
|
|
201
|
+
for (const e of report.extraOnStuck({ toolName: t.name, count: sig.count, kind: sig.kind })) {
|
|
202
|
+
yield await ctx.emit(e);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
yield await ctx.emit({
|
|
206
|
+
type: 'error',
|
|
207
|
+
sessionId: ctx.sessionId,
|
|
208
|
+
turnId: ctx.turnId,
|
|
209
|
+
source: 'system',
|
|
210
|
+
kind: 'fatal',
|
|
211
|
+
message: report.fatalMessage({ toolName: t.name, count: sig.count, how }),
|
|
212
|
+
});
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
118
217
|
//# sourceMappingURL=tool-dispatch.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-dispatch.js","sourceRoot":"","sources":["../src/tool-dispatch.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tool-dispatch.js","sourceRoot":"","sources":["../src/tool-dispatch.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard auth toolkit for channels — so a new channel gets consistent
|
|
3
|
+
* connection auth without re-rolling token handling. Two pieces:
|
|
4
|
+
*
|
|
5
|
+
* - {@link resolveChannelToken} — the standard `channels.<name>` token
|
|
6
|
+
* resolution (env → config → a generated, persisted secret). Never empty.
|
|
7
|
+
* - {@link bearerGuard} — a pre-connection handler: a constant-time bearer
|
|
8
|
+
* check a channel runs at its accept point (a WebSocket handshake, an HTTP
|
|
9
|
+
* request) before admitting a client.
|
|
10
|
+
*
|
|
11
|
+
* Channels that expose a network surface (mobile WS bridge, HTTP, web) should
|
|
12
|
+
* resolve their token with the former and gate connections with the latter,
|
|
13
|
+
* so the `channels.<name>.token` config + `MOXXY_<NAME>_TOKEN` env conventions
|
|
14
|
+
* are uniform across every channel.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { randomBytes } from 'node:crypto';
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import os from 'node:os';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
|
|
22
|
+
import { bearerTokenMatches } from './http-utils.js';
|
|
23
|
+
|
|
24
|
+
export interface ChannelTokenOptions {
|
|
25
|
+
/** Explicit token from channel config (`channels.<name>.token`). */
|
|
26
|
+
readonly configured?: string;
|
|
27
|
+
/** Env var checked first, e.g. `MOXXY_MOBILE_TOKEN`. */
|
|
28
|
+
readonly envVar?: string;
|
|
29
|
+
/** File (under the moxxy home dir) to persist a generated token, e.g. `mobile-token`. */
|
|
30
|
+
readonly fileName: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function moxxyHome(): string {
|
|
34
|
+
return process.env.MOXXY_HOME?.trim() || path.join(os.homedir(), '.moxxy');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resolve a channel's auth token. Precedence: `envVar` → `configured` → a
|
|
39
|
+
* 256-bit secret generated once and persisted (0600) under the moxxy home, so
|
|
40
|
+
* the same pairing secret survives restarts. Never returns an empty string —
|
|
41
|
+
* a network-reachable channel must always be authenticated.
|
|
42
|
+
*/
|
|
43
|
+
export function resolveChannelToken(opts: ChannelTokenOptions): string {
|
|
44
|
+
const fromEnv = (opts.envVar ? process.env[opts.envVar] : undefined)?.trim();
|
|
45
|
+
if (fromEnv) return fromEnv;
|
|
46
|
+
if (opts.configured && opts.configured.trim()) return opts.configured.trim();
|
|
47
|
+
|
|
48
|
+
const file = path.join(moxxyHome(), opts.fileName);
|
|
49
|
+
try {
|
|
50
|
+
const existing = fs.readFileSync(file, 'utf8').trim();
|
|
51
|
+
if (existing) return existing;
|
|
52
|
+
} catch {
|
|
53
|
+
// not yet created
|
|
54
|
+
}
|
|
55
|
+
const token = randomBytes(32).toString('hex');
|
|
56
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
57
|
+
fs.writeFileSync(file, token, { mode: 0o600 });
|
|
58
|
+
return token;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* A pre-connection auth handler: returns a guard that constant-time-compares a
|
|
63
|
+
* presented bearer token against `expected`. An empty `expected` always denies
|
|
64
|
+
* (never accidentally run an open surface).
|
|
65
|
+
*/
|
|
66
|
+
export function bearerGuard(expected: string): (presented: string | undefined | null) => boolean {
|
|
67
|
+
return (presented) => (expected ? bearerTokenMatches(presented, expected) : false);
|
|
68
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -149,6 +149,11 @@ export { isRetryableError, toFriendlyError, zodToJsonSchema, estimateTextTokens,
|
|
|
149
149
|
export { writeFileAtomic, moxxyHome, moxxyPath, type WriteFileAtomicOptions } from './fs-utils.js';
|
|
150
150
|
export { createMutex, type Mutex } from './mutex.js';
|
|
151
151
|
export { readRequestBody, bearerTokenMatches } from './http-utils.js';
|
|
152
|
+
export {
|
|
153
|
+
resolveChannelToken,
|
|
154
|
+
bearerGuard,
|
|
155
|
+
type ChannelTokenOptions,
|
|
156
|
+
} from './channel-auth.js';
|
|
152
157
|
export {
|
|
153
158
|
autoAllowResolver,
|
|
154
159
|
denyByDefaultResolver,
|
|
@@ -183,7 +188,12 @@ export {
|
|
|
183
188
|
type StuckLoopDetector,
|
|
184
189
|
type StuckSignal,
|
|
185
190
|
} from './mode-helpers.js';
|
|
186
|
-
export {
|
|
191
|
+
export {
|
|
192
|
+
dispatchToolCall,
|
|
193
|
+
executeToolUses,
|
|
194
|
+
emitRequestsAndDetectStuck,
|
|
195
|
+
type StuckLoopReport,
|
|
196
|
+
} from './tool-dispatch.js';
|
|
187
197
|
|
|
188
198
|
export type { TokenBudget, CompactContext, CompactorDef } from './compactor.js';
|
|
189
199
|
export {
|
package/src/tool-dispatch.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { asToolCallId } from './ids.js';
|
|
2
|
-
import type { MoxxyEvent } from './events.js';
|
|
2
|
+
import type { EmittedEvent, MoxxyEvent } from './events.js';
|
|
3
3
|
import type { ModeContext } from './mode.js';
|
|
4
4
|
import type { ToolCallVerdict } from './hooks.js';
|
|
5
|
-
import type { CollectedToolUse } from './mode-helpers.js';
|
|
5
|
+
import type { CollectedToolUse, StuckLoopDetector, StuckSignal } from './mode-helpers.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Execute a single tool-use end-to-end: dispatch `dispatchToolCall` hooks, run
|
|
@@ -11,7 +11,7 @@ import type { CollectedToolUse } from './mode-helpers.js';
|
|
|
11
11
|
* don't need the events can drain it (`for await (const _ of …) {}`) — either
|
|
12
12
|
* way the events reach the log via `ctx.emit`.
|
|
13
13
|
*
|
|
14
|
-
* Shared by every loop strategy (
|
|
14
|
+
* Shared by every loop strategy (default, goal, and any contributed mode) so
|
|
15
15
|
* the defensive outer try/catch below lives in ONE place. Without it, a throw
|
|
16
16
|
* from a hook handler / permission resolver / the emit itself escapes the
|
|
17
17
|
* generator and leaves this (and any later) call as an orphan
|
|
@@ -135,3 +135,136 @@ async function* emitDenied(
|
|
|
135
135
|
error: { kind: 'denied', message: reason },
|
|
136
136
|
});
|
|
137
137
|
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Run a batch of tool uses in order, handling mid-batch abort. On
|
|
141
|
+
* `ctx.signal.aborted` it synthesizes a failed `tool_result` for every
|
|
142
|
+
* not-yet-run call (so the log never ends on an orphan `tool_call_requested`),
|
|
143
|
+
* emits an `abort`, and returns `true` to tell the caller to stop. Returns
|
|
144
|
+
* `false` after a clean batch. Shared by every loop strategy so the
|
|
145
|
+
* orphan-on-abort guarantee lives in one place.
|
|
146
|
+
*/
|
|
147
|
+
export async function* executeToolUses(
|
|
148
|
+
ctx: ModeContext,
|
|
149
|
+
toolUses: ReadonlyArray<CollectedToolUse>,
|
|
150
|
+
iteration: number,
|
|
151
|
+
): AsyncGenerator<MoxxyEvent, boolean, unknown> {
|
|
152
|
+
// Tracks tool_call_requested events that haven't yet emitted a paired
|
|
153
|
+
// tool_result. On any early-exit (abort) we synthesize results for the
|
|
154
|
+
// leftovers so the event log can't end with orphan tool_call_requested events.
|
|
155
|
+
const unresolved = new Set<string>(toolUses.map((t) => t.id));
|
|
156
|
+
for (const t of toolUses) {
|
|
157
|
+
if (ctx.signal.aborted) {
|
|
158
|
+
for (const orphanId of unresolved) {
|
|
159
|
+
yield await ctx.emit({
|
|
160
|
+
type: 'tool_result',
|
|
161
|
+
sessionId: ctx.sessionId,
|
|
162
|
+
turnId: ctx.turnId,
|
|
163
|
+
source: 'tool',
|
|
164
|
+
callId: asToolCallId(orphanId),
|
|
165
|
+
ok: false,
|
|
166
|
+
error: { kind: 'aborted', message: 'turn aborted before tool ran' },
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
unresolved.clear();
|
|
170
|
+
yield await ctx.emit({
|
|
171
|
+
type: 'abort',
|
|
172
|
+
sessionId: ctx.sessionId,
|
|
173
|
+
turnId: ctx.turnId,
|
|
174
|
+
source: 'system',
|
|
175
|
+
reason: 'signal aborted during tool execution',
|
|
176
|
+
});
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
yield* dispatchToolCall(ctx, t, iteration);
|
|
181
|
+
} finally {
|
|
182
|
+
unresolved.delete(t.id);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* How a loop strategy phrases its stuck-loop abort. The skeleton — emit a
|
|
190
|
+
* `tool_call_requested` per call, feed the detector, and on a trip synthesize a
|
|
191
|
+
* failed `tool_result` for every already-emitted call before a fatal error — is
|
|
192
|
+
* identical across strategies and lives in {@link emitRequestsAndDetectStuck};
|
|
193
|
+
* only the wording, and goal mode's extra `goal_stuck` plugin_event, vary.
|
|
194
|
+
*/
|
|
195
|
+
export interface StuckLoopReport {
|
|
196
|
+
/** Message on the synthesized failed `tool_result` for the calls emitted
|
|
197
|
+
* before the trip (none of them ran). */
|
|
198
|
+
readonly abortedResultMessage: string;
|
|
199
|
+
/** Explanation for a `near` match (an `exact` match always reads
|
|
200
|
+
* "with identical input"). */
|
|
201
|
+
readonly nearHint: string;
|
|
202
|
+
/** Build the fatal error message shown to the user. */
|
|
203
|
+
readonly fatalMessage: (info: { toolName: string; count: number; how: string }) => string;
|
|
204
|
+
/** Extra events to emit right before the fatal error — goal mode surfaces a
|
|
205
|
+
* `goal_stuck` plugin_event here. */
|
|
206
|
+
readonly extraOnStuck?: (info: {
|
|
207
|
+
toolName: string;
|
|
208
|
+
count: number;
|
|
209
|
+
kind: StuckSignal['kind'];
|
|
210
|
+
}) => ReadonlyArray<EmittedEvent>;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Emit a `tool_call_requested` for each tool use and feed it to the stuck-loop
|
|
215
|
+
* detector. Returns `true` when the detector trips (caller should stop) — after
|
|
216
|
+
* synthesizing a failed `tool_result` for every request emitted this batch.
|
|
217
|
+
* That synthesis is load-bearing: a stuck trip bails WITHOUT running
|
|
218
|
+
* {@link executeToolUses}, so without it the emitted requests are left as orphan
|
|
219
|
+
* `tool_call_requested` events (which render as a tool stuck "running" forever
|
|
220
|
+
* and which the provider rejects next turn). Returns `false` when no trip fired.
|
|
221
|
+
*/
|
|
222
|
+
export async function* emitRequestsAndDetectStuck(
|
|
223
|
+
ctx: ModeContext,
|
|
224
|
+
toolUses: ReadonlyArray<CollectedToolUse>,
|
|
225
|
+
detector: StuckLoopDetector,
|
|
226
|
+
report: StuckLoopReport,
|
|
227
|
+
): AsyncGenerator<MoxxyEvent, boolean, unknown> {
|
|
228
|
+
const emitted: CollectedToolUse[] = [];
|
|
229
|
+
for (const t of toolUses) {
|
|
230
|
+
yield await ctx.emit({
|
|
231
|
+
type: 'tool_call_requested',
|
|
232
|
+
sessionId: ctx.sessionId,
|
|
233
|
+
turnId: ctx.turnId,
|
|
234
|
+
source: 'model',
|
|
235
|
+
callId: asToolCallId(t.id),
|
|
236
|
+
name: t.name,
|
|
237
|
+
input: t.input,
|
|
238
|
+
});
|
|
239
|
+
emitted.push(t);
|
|
240
|
+
const sig = detector.record(t.name, t.input);
|
|
241
|
+
if (!sig.stuck) continue;
|
|
242
|
+
for (const r of emitted) {
|
|
243
|
+
yield await ctx.emit({
|
|
244
|
+
type: 'tool_result',
|
|
245
|
+
sessionId: ctx.sessionId,
|
|
246
|
+
turnId: ctx.turnId,
|
|
247
|
+
source: 'tool',
|
|
248
|
+
callId: asToolCallId(r.id),
|
|
249
|
+
ok: false,
|
|
250
|
+
error: { kind: 'aborted', message: report.abortedResultMessage },
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
const how = sig.kind === 'near' ? report.nearHint : 'with identical input';
|
|
254
|
+
if (report.extraOnStuck) {
|
|
255
|
+
for (const e of report.extraOnStuck({ toolName: t.name, count: sig.count, kind: sig.kind })) {
|
|
256
|
+
yield await ctx.emit(e);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
yield await ctx.emit({
|
|
260
|
+
type: 'error',
|
|
261
|
+
sessionId: ctx.sessionId,
|
|
262
|
+
turnId: ctx.turnId,
|
|
263
|
+
source: 'system',
|
|
264
|
+
kind: 'fatal',
|
|
265
|
+
message: report.fatalMessage({ toolName: t.name, count: sig.count, how }),
|
|
266
|
+
});
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
return false;
|
|
270
|
+
}
|