agentgui 1.0.905 → 1.0.907
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/package.json +1 -1
- package/site/theme.mjs +94 -5
- package/test.js +70 -72
package/package.json
CHANGED
package/site/theme.mjs
CHANGED
|
@@ -26,17 +26,17 @@ function Hero() {
|
|
|
26
26
|
if (!home || !home.hero) return null;
|
|
27
27
|
return C.Panel({
|
|
28
28
|
style: 'margin:8px',
|
|
29
|
-
children:
|
|
29
|
+
children: h('div', { style: 'padding:24px 22px' },
|
|
30
30
|
C.Heading({ level: 1, style: 'margin:0 0 8px 0', children: home.hero.heading || site.title }),
|
|
31
31
|
home.hero.subheading ? C.Lede({ children: home.hero.subheading }) : null,
|
|
32
32
|
home.hero.body ? h('p', { style: 'margin:8px 0 16px 0;color:var(--panel-text-2);max-width:64ch' }, home.hero.body) : null,
|
|
33
|
-
(home.hero.badges && home.hero.badges.length) ? h('div', { style: 'display:flex;gap:6px;flex-wrap:wrap;margin
|
|
33
|
+
(home.hero.badges && home.hero.badges.length) ? h('div', { style: 'display:flex;gap:6px;flex-wrap:wrap;margin:0 0 12px 0' },
|
|
34
34
|
...home.hero.badges.map((b, i) => C.Chip({ key: 'b'+i, children: b.label }))
|
|
35
35
|
) : null,
|
|
36
36
|
(home.hero.ctas && home.hero.ctas.length) ? h('div', { style: 'display:flex;gap:8px;flex-wrap:wrap' },
|
|
37
37
|
...home.hero.ctas.map((c, i) => C.Btn({ key: 'c'+i, href: c.href, primary: c.primary, children: c.label }))
|
|
38
38
|
) : null
|
|
39
|
-
|
|
39
|
+
)
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -60,13 +60,102 @@ function Features() {
|
|
|
60
60
|
function Quickstart() {
|
|
61
61
|
if (!home || !home.quickstart || !home.quickstart.lines || !home.quickstart.lines.length) return null;
|
|
62
62
|
const lineNodes = home.quickstart.lines.map((l, i) => h('div', { key: 'q'+i, class: 'cli' },
|
|
63
|
-
h('span', { class: 'prompt' }, (l.kind === 'cmt' ? '#' : '
|
|
63
|
+
h('span', { class: 'prompt' }, (l.kind === 'cmt' ? '#' : '
|
|
64
|
+
|
|
65
|
+
function Examples() {
|
|
66
|
+
if (!home || !home.examples || !home.examples.items || !home.examples.items.length) return null;
|
|
67
|
+
const rows = home.examples.items.map((it, i) => C.RowLink({
|
|
68
|
+
key: 'e'+i,
|
|
69
|
+
title: it.name,
|
|
70
|
+
sub: it.desc || '',
|
|
71
|
+
meta: it.cta || 'open',
|
|
72
|
+
href: it.href || '#'
|
|
73
|
+
}));
|
|
74
|
+
return C.Panel({
|
|
75
|
+
title: home.examples.heading || 'examples',
|
|
76
|
+
style: 'margin:8px',
|
|
77
|
+
children: rows
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function Footer() {
|
|
82
|
+
return h('footer', { class: 'app-status' },
|
|
83
|
+
h('span', { class: 'item' }, 'styled with '),
|
|
84
|
+
h('a', { class: 'item', href: 'https://anentrypoint.github.io/design/' }, 'anentrypoint-design'),
|
|
85
|
+
h('span', { class: 'item' }, '·'),
|
|
86
|
+
h('a', { class: 'item', href: 'https://247420.xyz' }, '247420.xyz'),
|
|
87
|
+
h('span', { class: 'spread' }),
|
|
88
|
+
site.repo ? h('a', { class: 'item', href: site.repo }, 'source ↗') : null
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const navItems = (nav && nav.links ? nav.links : []).map(l => [String(l.label || ''), l.href]);
|
|
93
|
+
|
|
94
|
+
const App = C.AppShell({
|
|
95
|
+
topbar: C.Topbar({
|
|
96
|
+
brand: '247420',
|
|
97
|
+
leaf: site.title || '',
|
|
98
|
+
items: navItems
|
|
99
|
+
}),
|
|
100
|
+
crumb: C.Crumb({
|
|
101
|
+
trail: ['247420'],
|
|
102
|
+
leaf: site.title || ''
|
|
103
|
+
}),
|
|
104
|
+
main: h('div', {},
|
|
105
|
+
Hero(),
|
|
106
|
+
Features(),
|
|
107
|
+
Quickstart(),
|
|
108
|
+
Examples()
|
|
109
|
+
),
|
|
110
|
+
status: Footer()
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
applyDiff(document.getElementById('app'), [App]);
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
const html = ({ site, nav, home }) => `<!DOCTYPE html>
|
|
117
|
+
<html lang="en" class="ds-247420">
|
|
118
|
+
<head>
|
|
119
|
+
<meta charset="UTF-8" />
|
|
120
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
121
|
+
<title>${escapeHtml(site.title)}${site.tagline ? ' — ' + escapeHtml(site.tagline) : ''}</title>
|
|
122
|
+
<meta name="description" content="${escapeHtml(site.description || site.tagline || site.title)}" />
|
|
123
|
+
<meta property="og:title" content="${escapeHtml(site.title)}" />
|
|
124
|
+
<meta property="og:description" content="${escapeHtml(site.description || site.tagline || '')}" />
|
|
125
|
+
<meta property="og:url" content="${escapeHtml(site.url || '')}" />
|
|
126
|
+
<link rel="canonical" href="${escapeHtml(site.url || '')}" />
|
|
127
|
+
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Ctext y='26' font-size='26'%3E${encodeURIComponent(site.glyph || '◆')}%3C/text%3E%3C/svg%3E" />
|
|
128
|
+
<script type="importmap">{"imports":{"anentrypoint-design":"${SDK_URL}"}}</script>
|
|
129
|
+
<style>html,body{margin:0;padding:0}body{background:var(--app-bg,#FBF6EB);color:var(--ink,#1F1B16);font-family:var(--ff-ui,'Nunito',system-ui,sans-serif)}</style>
|
|
130
|
+
</head>
|
|
131
|
+
<body>
|
|
132
|
+
<div id="app"></div>
|
|
133
|
+
<script type="application/json" id="__site__">${escapeJson({ site, nav, home })}</script>
|
|
134
|
+
<script type="module">${clientScript}</script>
|
|
135
|
+
</body>
|
|
136
|
+
</html>
|
|
137
|
+
`;
|
|
138
|
+
|
|
139
|
+
export default {
|
|
140
|
+
render: async (ctx) => {
|
|
141
|
+
const site = ctx.readGlobal('site') || {};
|
|
142
|
+
const nav = ctx.readGlobal('navigation') || { links: [] };
|
|
143
|
+
const homeDoc = ctx.read('pages').docs.find(p => p.id === 'home');
|
|
144
|
+
if (!homeDoc) throw new Error('config/pages/home.yaml missing or has no id: home');
|
|
145
|
+
|
|
146
|
+
return [{
|
|
147
|
+
path: 'index.html',
|
|
148
|
+
html: html({ site, nav, home: homeDoc })
|
|
149
|
+
}];
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
)),
|
|
64
153
|
h('span', { class: 'cmd' }, l.text)
|
|
65
154
|
));
|
|
66
155
|
return C.Panel({
|
|
67
156
|
title: home.quickstart.heading || 'quick start',
|
|
68
157
|
style: 'margin:8px',
|
|
69
|
-
children: lineNodes
|
|
158
|
+
children: h('div', { style: 'padding:16px 22px' }, ...lineNodes)
|
|
70
159
|
});
|
|
71
160
|
}
|
|
72
161
|
|
package/test.js
CHANGED
|
@@ -14,10 +14,12 @@ import { maskKey, buildSystemPrompt } from './lib/provider-config.js';
|
|
|
14
14
|
import { buildBaseUrl, isRemoteRequest, encodeOAuthState, decodeOAuthState } from './lib/oauth-common.js';
|
|
15
15
|
import { compareVersions } from './lib/tool-version-check.js';
|
|
16
16
|
import { initializeDescriptors, getAgentDescriptor } from './lib/agent-descriptors.js';
|
|
17
|
+
import { createACPProtocolHandler } from './lib/acp-protocol.js';
|
|
18
|
+
import { sendJSON, compressAndSend, acceptsEncoding } from './lib/http-utils.js';
|
|
19
|
+
import { JsonlParser } from './lib/jsonl-parser.js';
|
|
17
20
|
const require = createRequire(import.meta.url);
|
|
18
21
|
let Database;
|
|
19
22
|
try { Database = (await import('bun:sqlite')).default; } catch { Database = require('better-sqlite3'); }
|
|
20
|
-
|
|
21
23
|
let passed = 0, failed = 0;
|
|
22
24
|
const ok = (name, fn) => Promise.resolve().then(fn).then(
|
|
23
25
|
() => { console.log(`ok — ${name}`); passed++; },
|
|
@@ -25,26 +27,26 @@ const ok = (name, fn) => Promise.resolve().then(fn).then(
|
|
|
25
27
|
function inMemDb() {
|
|
26
28
|
const db = new Database(':memory:');
|
|
27
29
|
if (db.pragma) db.pragma('foreign_keys = ON'); else db.run('PRAGMA foreign_keys = ON');
|
|
28
|
-
const
|
|
29
|
-
console.log = () => {}; console.warn = () => {};
|
|
30
|
+
const oL = console.log, oW = console.warn; console.log = () => {}; console.warn = () => {};
|
|
30
31
|
try { initSchema(db); migrateConversationColumns(db); migrateACPSchema(db); }
|
|
31
|
-
finally { console.log =
|
|
32
|
+
finally { console.log = oL; console.warn = oW; }
|
|
32
33
|
const gid = (p) => `${p}-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
33
34
|
return { db, prep: (sql) => db.prepare(sql), gid };
|
|
34
35
|
}
|
|
36
|
+
function mockRes() {
|
|
37
|
+
const r = { headers: {}, body: null, statusCode: 0 };
|
|
38
|
+
r.writeHead = (s, h) => { r.statusCode = s; r.headers = h; };
|
|
39
|
+
r.end = (b) => { r.body = b; };
|
|
40
|
+
return r;
|
|
41
|
+
}
|
|
35
42
|
const run = async () => {
|
|
36
43
|
await ok('codec: roundtrip + binary', () => {
|
|
37
|
-
|
|
38
|
-
assert.deepEqual(decode(encode(
|
|
39
|
-
const round = decode(encode({ bin: Buffer.from([1, 2, 3, 4]) }));
|
|
40
|
-
assert.deepEqual(Array.from(round.bin), [1, 2, 3, 4]);
|
|
44
|
+
assert.deepEqual(decode(encode({ a: 1, b: 'str', c: [1, 2, 3], d: { nested: true } })), { a: 1, b: 'str', c: [1, 2, 3], d: { nested: true } });
|
|
45
|
+
assert.deepEqual(Array.from(decode(encode({ bin: Buffer.from([1, 2, 3, 4]) })).bin), [1, 2, 3, 4]);
|
|
41
46
|
});
|
|
42
|
-
|
|
43
47
|
await ok('db: init schema creates conversations table', () => {
|
|
44
|
-
|
|
45
|
-
assert.ok(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='conversations'").get());
|
|
48
|
+
assert.ok(inMemDb().db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='conversations'").get());
|
|
46
49
|
});
|
|
47
|
-
|
|
48
50
|
await ok('db-queries: createConversation round-trip', () => {
|
|
49
51
|
const { db, prep, gid } = inMemDb();
|
|
50
52
|
const q = createQueries(db, prep, gid);
|
|
@@ -52,7 +54,6 @@ await ok('db-queries: createConversation round-trip', () => {
|
|
|
52
54
|
assert.equal(q.getConversation(c.id).title, 'Test');
|
|
53
55
|
assert.equal(q.getConversation(c.id).status, 'active');
|
|
54
56
|
});
|
|
55
|
-
|
|
56
57
|
await ok('db-queries: archive + restore + streaming flag', () => {
|
|
57
58
|
const { db, prep, gid } = inMemDb();
|
|
58
59
|
const q = createQueries(db, prep, gid);
|
|
@@ -62,7 +63,6 @@ await ok('db-queries: archive + restore + streaming flag', () => {
|
|
|
62
63
|
q.setIsStreaming(c.id, true); assert.equal(q.getIsStreaming(c.id), true);
|
|
63
64
|
q.setIsStreaming(c.id, false); assert.equal(q.getIsStreaming(c.id), false);
|
|
64
65
|
});
|
|
65
|
-
|
|
66
66
|
await ok('acp-queries: thread crud + search', () => {
|
|
67
67
|
const { db, prep, gid } = inMemDb();
|
|
68
68
|
const q = createQueries(db, prep, gid);
|
|
@@ -73,13 +73,11 @@ await ok('acp-queries: thread crud + search', () => {
|
|
|
73
73
|
q.createThread({ kind: 'b' });
|
|
74
74
|
assert.equal(q.searchThreads({}).total, 2);
|
|
75
75
|
});
|
|
76
|
-
|
|
77
76
|
await ok('WsRouter: dispatch + 404 + error + legacy', async () => {
|
|
78
77
|
const router = new WsRouter();
|
|
79
78
|
router.handle('ping', async (p) => ({ pong: p.n }));
|
|
80
79
|
router.handle('boom', async () => { throw Object.assign(new Error('kaboom'), { code: 422 }); });
|
|
81
|
-
let legacy = null;
|
|
82
|
-
router.onLegacy((m) => { legacy = m; });
|
|
80
|
+
let legacy = null; router.onLegacy((m) => { legacy = m; });
|
|
83
81
|
const replies = [];
|
|
84
82
|
const ws = { readyState: 1, send: (b) => replies.push(decode(b)), clientId: 'c' };
|
|
85
83
|
await router.onMessage(ws, encode({ r: 1, m: 'ping', p: { n: 7 } }));
|
|
@@ -87,11 +85,8 @@ await ok('WsRouter: dispatch + 404 + error + legacy', async () => {
|
|
|
87
85
|
await router.onMessage(ws, encode({ r: 3, m: 'boom', p: {} }));
|
|
88
86
|
await router.onMessage(ws, encode({ type: 'subscribe', id: 'x' }));
|
|
89
87
|
assert.deepEqual(replies[0], { r: 1, d: { pong: 7 } });
|
|
90
|
-
assert.equal(replies[1].e.c, 404);
|
|
91
|
-
assert.equal(replies[2].e.c, 422);
|
|
92
|
-
assert.equal(legacy.type, 'subscribe');
|
|
88
|
+
assert.equal(replies[1].e.c, 404); assert.equal(replies[2].e.c, 422); assert.equal(legacy.type, 'subscribe');
|
|
93
89
|
});
|
|
94
|
-
|
|
95
90
|
await ok('machines: tool-install + execution + acp-server lifecycle', () => {
|
|
96
91
|
assert.ok(tim.getMachineActors() instanceof Map);
|
|
97
92
|
assert.equal(exm.snapshot('nonexistent-conv-id'), null);
|
|
@@ -102,99 +97,102 @@ await ok('machines: tool-install + execution + acp-server lifecycle', () => {
|
|
|
102
97
|
assert.equal(asm.isHealthy('test-tool'), true);
|
|
103
98
|
asm.stopAll();
|
|
104
99
|
});
|
|
105
|
-
|
|
106
100
|
await ok('workflow-plugin + agent-registry hermes', async () => {
|
|
107
101
|
const wp = await import('./lib/plugins/workflow-plugin.js');
|
|
108
102
|
assert.deepEqual(wp.default.dependencies, ['database']);
|
|
109
103
|
const { registry } = await import('./lib/claude-runner-agents.js');
|
|
110
104
|
const h = registry.get('hermes');
|
|
111
|
-
assert.equal(h.protocol, 'acp');
|
|
112
|
-
assert.deepEqual(h.buildArgs(), ['acp']);
|
|
105
|
+
assert.equal(h.protocol, 'acp'); assert.deepEqual(h.buildArgs(), ['acp']);
|
|
113
106
|
});
|
|
114
|
-
|
|
115
107
|
await ok('delete-all: soft-deletes + wipes related', () => {
|
|
116
108
|
const { db, prep, gid } = inMemDb();
|
|
117
109
|
const q = createQueries(db, prep, gid);
|
|
118
110
|
const c1 = q.createConversation('claude-code', 'A');
|
|
119
111
|
q.createConversation('claude-code', 'B');
|
|
120
|
-
q.createSession(c1.id);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const rows = db.prepare('SELECT status, count(*) as c FROM conversations GROUP BY status').all();
|
|
125
|
-
assert.deepEqual(rows, [{ status: 'deleted', c: 2 }]);
|
|
112
|
+
q.createSession(c1.id); q.createMessage(c1.id, 'user', 'hello');
|
|
113
|
+
const oL = console.log; console.log = () => {};
|
|
114
|
+
try { q.deleteAllConversations(); } finally { console.log = oL; }
|
|
115
|
+
assert.deepEqual(db.prepare('SELECT status, count(*) as c FROM conversations GROUP BY status').all(), [{ status: 'deleted', c: 2 }]);
|
|
126
116
|
assert.equal(db.prepare('SELECT count(*) as c FROM messages').get().c, 0);
|
|
127
117
|
assert.equal(q.getConversationsList().length, 0);
|
|
128
118
|
});
|
|
129
|
-
|
|
130
119
|
await ok('provider-config: maskKey + buildSystemPrompt', () => {
|
|
131
|
-
assert.equal(maskKey(''), '****');
|
|
132
|
-
assert.equal(maskKey('short'), '****');
|
|
133
|
-
assert.equal(maskKey('sk-abcd1234efgh'), '****efgh');
|
|
120
|
+
assert.equal(maskKey(''), '****'); assert.equal(maskKey('short'), '****'); assert.equal(maskKey('sk-abcd1234efgh'), '****efgh');
|
|
134
121
|
assert.equal(buildSystemPrompt('claude-code'), '');
|
|
135
122
|
assert.equal(buildSystemPrompt('opencode', 'sonnet'), 'Use opencode subagent for all tasks. Model: sonnet.');
|
|
136
123
|
assert.equal(buildSystemPrompt('foo-·-bar', null, 'sub'), 'Use foo subagent for all tasks. Subagent: sub.');
|
|
137
124
|
});
|
|
138
|
-
|
|
139
125
|
await ok('oauth-common: state codec + url helpers', () => {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
assert.equal(
|
|
144
|
-
const fallback = decodeOAuthState('not-base64-json');
|
|
145
|
-
assert.equal(fallback.csrfToken, 'not-base64-json');
|
|
146
|
-
assert.equal(fallback.relayUrl, null);
|
|
126
|
+
const dec = decodeOAuthState(encodeOAuthState('csrf-tok', 'https://relay.test/cb'));
|
|
127
|
+
assert.equal(dec.csrfToken, 'csrf-tok'); assert.equal(dec.relayUrl, 'https://relay.test/cb');
|
|
128
|
+
const fb = decodeOAuthState('not-base64-json');
|
|
129
|
+
assert.equal(fb.csrfToken, 'not-base64-json'); assert.equal(fb.relayUrl, null);
|
|
147
130
|
const reqRemote = { headers: { 'x-forwarded-host': 'a.com', 'x-forwarded-proto': 'https' }, socket: {} };
|
|
148
|
-
assert.equal(buildBaseUrl(reqRemote, 3000), 'https://a.com');
|
|
149
|
-
assert.equal(isRemoteRequest(reqRemote), true);
|
|
131
|
+
assert.equal(buildBaseUrl(reqRemote, 3000), 'https://a.com'); assert.equal(isRemoteRequest(reqRemote), true);
|
|
150
132
|
assert.equal(buildBaseUrl({ headers: {}, socket: {} }, 3000), 'http://127.0.0.1:3000');
|
|
151
133
|
assert.equal(isRemoteRequest({ headers: {} }), false);
|
|
152
134
|
});
|
|
153
|
-
|
|
154
135
|
await ok('tool-version-check: compareVersions', () => {
|
|
155
|
-
assert.equal(compareVersions('1.0.0', '1.0.1'), true);
|
|
156
|
-
assert.equal(compareVersions('1.0.
|
|
157
|
-
assert.equal(compareVersions('1.0.0', '1.0.0'), false);
|
|
158
|
-
assert.equal(compareVersions('1.2', '1.2.1'), true);
|
|
136
|
+
assert.equal(compareVersions('1.0.0', '1.0.1'), true); assert.equal(compareVersions('1.0.1', '1.0.0'), false);
|
|
137
|
+
assert.equal(compareVersions('1.0.0', '1.0.0'), false); assert.equal(compareVersions('1.2', '1.2.1'), true);
|
|
159
138
|
assert.equal(compareVersions('2.0.0', '1.99.99'), false);
|
|
160
|
-
assert.equal(compareVersions(null, '1.0.0'), false);
|
|
161
|
-
assert.equal(compareVersions('1.0.0', null), false);
|
|
139
|
+
assert.equal(compareVersions(null, '1.0.0'), false); assert.equal(compareVersions('1.0.0', null), false);
|
|
162
140
|
});
|
|
163
|
-
|
|
164
141
|
await ok('agent-descriptors: initialize + cache', () => {
|
|
165
|
-
|
|
166
|
-
assert.equal(n, 1);
|
|
142
|
+
assert.equal(initializeDescriptors([{ id: 'claude-code', name: 'Claude Code', path: '/x' }]), 1);
|
|
167
143
|
const d = getAgentDescriptor('claude-code');
|
|
168
144
|
assert.equal(d.metadata.ref.name, 'Claude Code');
|
|
169
|
-
assert.ok(d.specs.input.properties.model);
|
|
170
|
-
assert.ok(d.specs.thread_state.properties.sessionId);
|
|
145
|
+
assert.ok(d.specs.input.properties.model); assert.ok(d.specs.thread_state.properties.sessionId);
|
|
171
146
|
assert.equal(getAgentDescriptor('nope'), null);
|
|
172
147
|
});
|
|
173
|
-
|
|
174
148
|
await ok('ws-optimizer: high-priority flushes immediately', () => {
|
|
175
|
-
const opt = new WSOptimizer();
|
|
176
|
-
const sent = [];
|
|
149
|
+
const opt = new WSOptimizer(); const sent = [];
|
|
177
150
|
const ws = { readyState: 1, clientId: 'c1', send: (b) => sent.push(decode(b)) };
|
|
178
151
|
opt.sendToClient(ws, { type: 'streaming_start', id: 1 });
|
|
179
|
-
assert.equal(sent.length, 1);
|
|
180
|
-
assert.equal(
|
|
181
|
-
opt.removeClient(ws);
|
|
182
|
-
assert.equal(opt.getStats().clients, 0);
|
|
152
|
+
assert.equal(sent.length, 1); assert.equal(sent[0].type, 'streaming_start');
|
|
153
|
+
opt.removeClient(ws); assert.equal(opt.getStats().clients, 0);
|
|
183
154
|
});
|
|
184
|
-
|
|
185
155
|
await ok('ws-optimizer: low-priority batches via timer', async () => {
|
|
186
|
-
const opt = new WSOptimizer();
|
|
187
|
-
const sent = [];
|
|
156
|
+
const opt = new WSOptimizer(); const sent = [];
|
|
188
157
|
const ws = { readyState: 1, clientId: 'c2', latencyTier: 'excellent', send: (b) => sent.push(decode(b)) };
|
|
189
|
-
opt.sendToClient(ws, { type: 'tts_audio', n: 1 });
|
|
190
|
-
opt.sendToClient(ws, { type: 'tts_audio', n: 2 });
|
|
158
|
+
opt.sendToClient(ws, { type: 'tts_audio', n: 1 }); opt.sendToClient(ws, { type: 'tts_audio', n: 2 });
|
|
191
159
|
assert.equal(sent.length, 0);
|
|
192
160
|
await new Promise(r => setTimeout(r, 40));
|
|
193
|
-
assert.equal(sent.length, 1);
|
|
194
|
-
assert.equal(sent[0].length, 2);
|
|
161
|
+
assert.equal(sent.length, 1); assert.equal(sent[0].length, 2);
|
|
195
162
|
opt.removeClient(ws);
|
|
196
163
|
});
|
|
197
|
-
|
|
164
|
+
await ok('acp-protocol: session/update + result + error mapping', () => {
|
|
165
|
+
const h = createACPProtocolHandler(); const ctx = { sessionId: 's1' };
|
|
166
|
+
assert.equal(h(null, ctx), null);
|
|
167
|
+
const chunk = h({ method: 'session/update', params: { sessionId: 's1', update: { sessionUpdate: 'agent_message_chunk', content: 'hi' } } }, ctx);
|
|
168
|
+
assert.equal(chunk.type, 'assistant'); assert.equal(chunk.message.content[0].text, 'hi');
|
|
169
|
+
const tc = h({ method: 'session/update', params: { sessionId: 's1', update: { sessionUpdate: 'tool_call', toolCallId: 'x', kind: 'read', rawInput: { p: 1 } } } }, ctx);
|
|
170
|
+
assert.equal(tc.message.content[0].type, 'tool_use');
|
|
171
|
+
assert.equal(h({ id: 1, result: { stopReason: 'end_turn', usage: {} } }, ctx).type, 'result');
|
|
172
|
+
assert.equal(h({ method: 'error', error: { message: 'x' } }, ctx).type, 'error');
|
|
173
|
+
assert.equal(h({ method: 'session/update', params: { sessionId: 's1', update: { sessionUpdate: 'unknown' } } }, ctx), null);
|
|
174
|
+
});
|
|
175
|
+
await ok('http-utils: sendJSON + compressAndSend size threshold', () => {
|
|
176
|
+
const req = { headers: { 'accept-encoding': 'gzip, deflate' } };
|
|
177
|
+
assert.equal(acceptsEncoding(req, 'gzip'), true); assert.equal(acceptsEncoding({ headers: {} }, 'gzip'), false);
|
|
178
|
+
const small = mockRes(); sendJSON(req, small, 200, { ok: 1 });
|
|
179
|
+
assert.equal(small.statusCode, 200); assert.equal(small.headers['Content-Type'], 'application/json');
|
|
180
|
+
assert.ok(!small.headers['Content-Encoding']);
|
|
181
|
+
const big = mockRes(); compressAndSend(req, big, 200, 'text/plain', 'x'.repeat(2000));
|
|
182
|
+
assert.equal(big.headers['Content-Encoding'], 'gzip');
|
|
183
|
+
const noGz = mockRes(); compressAndSend({ headers: {} }, noGz, 200, 'text/html', 'y'.repeat(2000));
|
|
184
|
+
assert.equal(noGz.headers['Cache-Control'], 'no-store'); assert.ok(!noGz.headers['Content-Encoding']);
|
|
185
|
+
});
|
|
186
|
+
await ok('jsonl-parser: register + remove + clear', () => {
|
|
187
|
+
const calls = []; const fakeQ = { getConversationByClaudeSessionId: () => null };
|
|
188
|
+
const p = new JsonlParser({ broadcastSync: (m) => calls.push(m), queries: fakeQ });
|
|
189
|
+
p.registerSession('claude-1', 'conv-1', 'db-sess-1');
|
|
190
|
+
assert.equal(p._convMap.get('claude-1'), 'conv-1'); assert.equal(p._sessions.get('claude-1'), 'db-sess-1');
|
|
191
|
+
p.removeSid('claude-1');
|
|
192
|
+
assert.equal(p._convMap.has('claude-1'), false); assert.equal(p._sessions.has('claude-1'), false);
|
|
193
|
+
p.registerSession('s2', 'c2', 'd2'); p.clear();
|
|
194
|
+
assert.equal(p._convMap.size, 0); assert.equal(p._sessions.size, 0); assert.equal(calls.length, 0);
|
|
195
|
+
});
|
|
198
196
|
console.log(`\n${passed} passed, ${failed} failed`);
|
|
199
197
|
process.exit(failed === 0 ? 0 : 1);
|
|
200
198
|
}; run();
|