mu-harness 0.19.0 → 0.20.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/esm/agents/registry.d.ts +0 -10
- package/esm/channels/adapter.d.ts +26 -0
- package/esm/channels/adapter.js +27 -0
- package/esm/channels/channel.d.ts +1 -1
- package/esm/channels/channel.js +9 -5
- package/esm/channels/index.d.ts +2 -0
- package/esm/channels/index.js +2 -0
- package/esm/channels/manager.d.ts +1 -1
- package/esm/channels/ws/client.d.ts +24 -0
- package/esm/channels/ws/client.js +303 -0
- package/esm/channels/ws/index.d.ts +6 -0
- package/esm/channels/ws/index.js +6 -0
- package/esm/channels/ws/protocol.d.ts +242 -0
- package/esm/channels/ws/protocol.js +103 -0
- package/esm/channels/ws/server.d.ts +15 -0
- package/esm/channels/ws/server.js +310 -0
- package/esm/channels/ws/session-service.d.ts +36 -0
- package/esm/channels/ws/session-service.js +55 -0
- package/esm/channels/ws/sub-agent.d.ts +8 -0
- package/esm/channels/ws/sub-agent.js +72 -0
- package/esm/channels/ws/wire-events.d.ts +3 -0
- package/esm/channels/ws/wire-events.js +38 -0
- package/esm/channels/ws/wire.d.ts +36 -0
- package/esm/channels/ws/wire.js +46 -0
- package/esm/channels/ws/ws-client.d.ts +13 -0
- package/esm/channels/ws/ws-client.js +55 -0
- package/esm/commands/skill.d.ts +0 -5
- package/esm/commands/skill.js +0 -5
- package/esm/common/utils.d.ts +0 -1
- package/esm/common/utils.js +0 -1
- package/esm/harness/types.d.ts +0 -13
- package/esm/session/manager.d.ts +0 -1
- package/esm/skills/loader.d.ts +0 -8
- package/esm/skills/loader.js +0 -8
- package/esm/skills/platform.d.ts +0 -5
- package/esm/skills/platform.js +0 -5
- package/esm/skills/registry.d.ts +0 -4
- package/esm/skills/types.d.ts +0 -5
- package/esm/subAgents/runner.d.ts +0 -4
- package/esm/tui/chat/ChatApp.d.ts +0 -34
- package/esm/tui/chat/ChatApp.js +0 -5
- package/esm/tui/chat/status.d.ts +0 -1
- package/esm/tui/chat/transcript.d.ts +0 -1
- package/esm/tui/chat/transcript.js +0 -1
- package/esm/tui/index.d.ts +0 -5
- package/esm/tui/index.js +0 -5
- package/package.json +4 -3
- package/script/agents/registry.d.ts +0 -10
- package/script/channels/adapter.d.ts +26 -0
- package/script/channels/adapter.js +30 -0
- package/script/channels/channel.d.ts +1 -1
- package/script/channels/channel.js +9 -5
- package/script/channels/index.d.ts +2 -0
- package/script/channels/index.js +18 -1
- package/script/channels/manager.d.ts +1 -1
- package/script/channels/ws/client.d.ts +24 -0
- package/script/channels/ws/client.js +306 -0
- package/script/channels/ws/index.d.ts +6 -0
- package/script/channels/ws/index.js +28 -0
- package/script/channels/ws/protocol.d.ts +242 -0
- package/script/channels/ws/protocol.js +107 -0
- package/script/channels/ws/server.d.ts +15 -0
- package/script/channels/ws/server.js +313 -0
- package/script/channels/ws/session-service.d.ts +36 -0
- package/script/channels/ws/session-service.js +58 -0
- package/script/channels/ws/sub-agent.d.ts +8 -0
- package/script/channels/ws/sub-agent.js +75 -0
- package/script/channels/ws/wire-events.d.ts +3 -0
- package/script/channels/ws/wire-events.js +41 -0
- package/script/channels/ws/wire.d.ts +36 -0
- package/script/channels/ws/wire.js +54 -0
- package/script/channels/ws/ws-client.d.ts +13 -0
- package/script/channels/ws/ws-client.js +58 -0
- package/script/commands/skill.d.ts +0 -5
- package/script/commands/skill.js +0 -5
- package/script/common/utils.d.ts +0 -1
- package/script/common/utils.js +0 -1
- package/script/harness/types.d.ts +0 -13
- package/script/session/manager.d.ts +0 -1
- package/script/skills/loader.d.ts +0 -8
- package/script/skills/loader.js +0 -8
- package/script/skills/platform.d.ts +0 -5
- package/script/skills/platform.js +0 -5
- package/script/skills/registry.d.ts +0 -4
- package/script/skills/types.d.ts +0 -5
- package/script/subAgents/runner.d.ts +0 -4
- package/script/tui/chat/ChatApp.d.ts +0 -34
- package/script/tui/chat/ChatApp.js +0 -5
- package/script/tui/chat/status.d.ts +0 -1
- package/script/tui/chat/transcript.d.ts +0 -1
- package/script/tui/chat/transcript.js +0 -1
- package/script/tui/index.d.ts +0 -5
- package/script/tui/index.js +0 -5
package/esm/agents/registry.d.ts
CHANGED
|
@@ -2,17 +2,7 @@ import type { Agent, ToolDecision } from './types.js';
|
|
|
2
2
|
export interface AgentRegistry {
|
|
3
3
|
list(): Agent[];
|
|
4
4
|
get(name: string): Agent | undefined;
|
|
5
|
-
/**
|
|
6
|
-
* Register (or replace) an agent at runtime — mirrors {@link SkillRegistry.add}.
|
|
7
|
-
* Lets tools like `create_agent` make a freshly-authored agent immediately
|
|
8
|
-
* delegatable without a restart. Agents that `extends` the added one are
|
|
9
|
-
* re-resolved so they pick up the change.
|
|
10
|
-
*/
|
|
11
5
|
add(agent: Agent): void;
|
|
12
|
-
/**
|
|
13
|
-
* Replace the entire set in place (rebuild) — used by hot-reload to reflect
|
|
14
|
-
* created, edited, and deleted definitions. Existing references see the new set.
|
|
15
|
-
*/
|
|
16
6
|
replaceAll(agents: Agent[]): void;
|
|
17
7
|
}
|
|
18
8
|
export declare const grantArg: (tool: string, input: unknown) => string | undefined;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ApprovalManager } from '../permissions/index.js';
|
|
2
|
+
import type { Harness } from '../harness/types.js';
|
|
3
|
+
import type { ChannelManager } from './types.js';
|
|
4
|
+
export interface ChannelAdapterContext {
|
|
5
|
+
readonly harness: Harness;
|
|
6
|
+
readonly manager: ChannelManager;
|
|
7
|
+
readonly approvals: ApprovalManager;
|
|
8
|
+
}
|
|
9
|
+
export interface ChannelAdapterHandle {
|
|
10
|
+
stop(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export interface ChannelAdapter {
|
|
13
|
+
readonly name: string;
|
|
14
|
+
start(ctx: ChannelAdapterContext): Promise<ChannelAdapterHandle>;
|
|
15
|
+
}
|
|
16
|
+
export interface RunChannelsOptions {
|
|
17
|
+
harness: Harness;
|
|
18
|
+
approvals: ApprovalManager;
|
|
19
|
+
adapters: ChannelAdapter[];
|
|
20
|
+
idGen?: () => string;
|
|
21
|
+
}
|
|
22
|
+
export interface ChannelHost {
|
|
23
|
+
readonly manager: ChannelManager;
|
|
24
|
+
stop(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export declare function runChannels(opts: RunChannelsOptions): Promise<ChannelHost>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createChannelManager } from './manager.js';
|
|
2
|
+
export async function runChannels(opts) {
|
|
3
|
+
const { harness, approvals, adapters, idGen } = opts;
|
|
4
|
+
// Channels bind to a SPECIFIC session id: reopen it from disk if it exists,
|
|
5
|
+
// else create it. This is what lets a network adapter address persisted
|
|
6
|
+
// sessions by id (a plain `() => create()` could only ever make new ones).
|
|
7
|
+
const manager = createChannelManager({
|
|
8
|
+
createSession: async (id) => {
|
|
9
|
+
const stored = await harness.sessions.read(id);
|
|
10
|
+
return stored ? await harness.sessions.open(id) : harness.sessions.create({ id });
|
|
11
|
+
},
|
|
12
|
+
idGen,
|
|
13
|
+
});
|
|
14
|
+
const handles = [];
|
|
15
|
+
for (const adapter of adapters) {
|
|
16
|
+
handles.push(await adapter.start({ harness, manager, approvals }));
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
manager,
|
|
20
|
+
stop: async () => {
|
|
21
|
+
for (const handle of handles.reverse())
|
|
22
|
+
await handle.stop();
|
|
23
|
+
for (const channel of manager.list())
|
|
24
|
+
manager.close(channel.id);
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
package/esm/channels/channel.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { createEmitter } from '../common/index.js';
|
|
2
2
|
export const createChannel = (config) => {
|
|
3
3
|
let session;
|
|
4
|
+
let pending;
|
|
4
5
|
const emitter = createEmitter();
|
|
5
6
|
const ensure = () => {
|
|
6
|
-
if (!
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
if (!pending) {
|
|
8
|
+
pending = Promise.resolve(config.createSession(config.id)).then((s) => {
|
|
9
|
+
session = s;
|
|
10
|
+
s.subscribe(emitter.emit);
|
|
11
|
+
return s;
|
|
12
|
+
});
|
|
9
13
|
}
|
|
10
|
-
return
|
|
14
|
+
return pending;
|
|
11
15
|
};
|
|
12
16
|
return {
|
|
13
17
|
id: config.id,
|
|
@@ -18,7 +22,7 @@ export const createChannel = (config) => {
|
|
|
18
22
|
get messages() {
|
|
19
23
|
return session?.messages ?? [];
|
|
20
24
|
},
|
|
21
|
-
send: (input) => ensure().send(input),
|
|
25
|
+
send: async (input) => (await ensure()).send(input),
|
|
22
26
|
abort: () => session?.abort(),
|
|
23
27
|
subscribe: emitter.subscribe,
|
|
24
28
|
};
|
package/esm/channels/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export type { Channel, ChannelEvent, ChannelManager, ChannelManagerEvent } from './types.js';
|
|
2
2
|
export { createChannel } from './channel.js';
|
|
3
3
|
export { createChannelManager } from './manager.js';
|
|
4
|
+
export { type ChannelAdapter, type ChannelAdapterContext, type ChannelAdapterHandle, type ChannelHost, runChannels, type RunChannelsOptions, } from './adapter.js';
|
|
5
|
+
export * from './ws/index.js';
|
package/esm/channels/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentSession } from '../session/index.js';
|
|
2
2
|
import type { ChannelManager } from './types.js';
|
|
3
3
|
export declare const createChannelManager: (config: {
|
|
4
|
-
createSession: () => AgentSession
|
|
4
|
+
createSession: (id: string) => AgentSession | Promise<AgentSession>;
|
|
5
5
|
idGen?: () => string;
|
|
6
6
|
}) => ChannelManager;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ChatFeatures, ChatHost } from '../../tui/index.js';
|
|
2
|
+
export interface ConnectHarnessOptions {
|
|
3
|
+
url: string;
|
|
4
|
+
token?: string;
|
|
5
|
+
sessionId?: string;
|
|
6
|
+
cwd: string;
|
|
7
|
+
initialTheme?: string;
|
|
8
|
+
saveTheme?: (name: string) => void;
|
|
9
|
+
initialThinking?: boolean;
|
|
10
|
+
saveThinking?: (visible: boolean) => void;
|
|
11
|
+
history?: {
|
|
12
|
+
load(): string[];
|
|
13
|
+
append(text: string): void;
|
|
14
|
+
};
|
|
15
|
+
banner?: string;
|
|
16
|
+
minimal?: boolean;
|
|
17
|
+
features?: ChatFeatures;
|
|
18
|
+
onExit?: (code: number) => void;
|
|
19
|
+
}
|
|
20
|
+
export interface RemoteHarness {
|
|
21
|
+
host: ChatHost;
|
|
22
|
+
close(): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
export declare function connectHarness(opts: ConnectHarnessOptions): Promise<RemoteHarness>;
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { createWsClient } from './ws-client.js';
|
|
2
|
+
import { textOf } from './wire.js';
|
|
3
|
+
function deferred() {
|
|
4
|
+
let resolve;
|
|
5
|
+
const promise = new Promise((r) => (resolve = r));
|
|
6
|
+
return { promise, resolve };
|
|
7
|
+
}
|
|
8
|
+
const parseArgs = (raw) => {
|
|
9
|
+
try {
|
|
10
|
+
return JSON.parse(raw);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return raw;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
function wireToEvents(wire) {
|
|
17
|
+
if (wire.role !== 'assistant')
|
|
18
|
+
return [];
|
|
19
|
+
const events = [];
|
|
20
|
+
for (const tc of wire.toolCalls ?? []) {
|
|
21
|
+
events.push({ type: 'tool_call', id: tc.id, name: tc.function.name, input: parseArgs(tc.function.arguments) });
|
|
22
|
+
}
|
|
23
|
+
events.push({ type: 'message', message: { role: 'assistant', content: [{ type: 'text', text: wire.content }] } });
|
|
24
|
+
return events;
|
|
25
|
+
}
|
|
26
|
+
export async function connectHarness(opts) {
|
|
27
|
+
const client = createWsClient({ url: opts.url, token: opts.token, sessionId: opts.sessionId });
|
|
28
|
+
// Caches hydrated from connect-time frames (feed the synchronous host methods).
|
|
29
|
+
let agents = [];
|
|
30
|
+
let activeAgentId = null;
|
|
31
|
+
let models = [];
|
|
32
|
+
let modelSelected = '';
|
|
33
|
+
let commands = [];
|
|
34
|
+
const sessionsById = new Map();
|
|
35
|
+
const subSessions = new Map();
|
|
36
|
+
const approvalListeners = new Set();
|
|
37
|
+
const subAgentListeners = new Set();
|
|
38
|
+
const forkWaiters = new Map();
|
|
39
|
+
const subagentWaiters = new Map();
|
|
40
|
+
const listWaiters = [];
|
|
41
|
+
const modelWaiters = [];
|
|
42
|
+
const rawWaiters = new Map();
|
|
43
|
+
const agentsReady = deferred();
|
|
44
|
+
const route = (sessionId, event) => sessionsById.get(sessionId)?._emit(event);
|
|
45
|
+
const applyRaw = (sessionId, messages) => {
|
|
46
|
+
sessionsById.get(sessionId)?._setMessages([...messages]);
|
|
47
|
+
rawWaiters.get(sessionId)?.(messages);
|
|
48
|
+
rawWaiters.delete(sessionId);
|
|
49
|
+
};
|
|
50
|
+
const makeFakeSession = (id, initial, readonly = false) => {
|
|
51
|
+
let messages = [...initial];
|
|
52
|
+
const listeners = new Set();
|
|
53
|
+
return {
|
|
54
|
+
id,
|
|
55
|
+
get messages() {
|
|
56
|
+
return messages;
|
|
57
|
+
},
|
|
58
|
+
tools: [],
|
|
59
|
+
send: (input) => {
|
|
60
|
+
if (!readonly) {
|
|
61
|
+
client.send({ type: 'chat', sessionId: id, text: typeof input === 'string' ? input : textOf(input) });
|
|
62
|
+
}
|
|
63
|
+
return Promise.resolve();
|
|
64
|
+
},
|
|
65
|
+
abort: () => {
|
|
66
|
+
if (!readonly)
|
|
67
|
+
client.send({ type: 'abort', sessionId: id });
|
|
68
|
+
},
|
|
69
|
+
subscribe: (l) => {
|
|
70
|
+
listeners.add(l);
|
|
71
|
+
return () => listeners.delete(l);
|
|
72
|
+
},
|
|
73
|
+
_emit: (event) => {
|
|
74
|
+
for (const l of [...listeners])
|
|
75
|
+
l(event);
|
|
76
|
+
},
|
|
77
|
+
_setMessages: (m) => {
|
|
78
|
+
messages = m;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
const handleSubAgentEvent = (ev) => {
|
|
83
|
+
if (ev.type === 'started') {
|
|
84
|
+
const session = makeFakeSession(ev.runId, [], true);
|
|
85
|
+
subSessions.set(ev.runId, session);
|
|
86
|
+
const run = { runId: ev.runId, agent: ev.agentName, parentId: ev.parentSessionId, session };
|
|
87
|
+
for (const l of [...subAgentListeners])
|
|
88
|
+
l(run);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const session = subSessions.get(ev.runId);
|
|
92
|
+
if (!session)
|
|
93
|
+
return;
|
|
94
|
+
switch (ev.type) {
|
|
95
|
+
case 'content':
|
|
96
|
+
session._emit({ type: 'text', text: ev.detail ?? '' });
|
|
97
|
+
return;
|
|
98
|
+
case 'tool_call':
|
|
99
|
+
session._emit({
|
|
100
|
+
type: 'tool_call',
|
|
101
|
+
id: crypto.randomUUID(),
|
|
102
|
+
name: ev.detail?.name ?? '',
|
|
103
|
+
input: parseArgs(ev.detail?.arguments ?? '{}'),
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
case 'completed':
|
|
107
|
+
session._setMessages([{ role: 'assistant', content: [{ type: 'text', text: ev.detail?.content ?? '' }] }]);
|
|
108
|
+
session._emit({ type: 'turn_end' });
|
|
109
|
+
subSessions.delete(ev.runId);
|
|
110
|
+
return;
|
|
111
|
+
case 'error':
|
|
112
|
+
session._emit({ type: 'error', error: new Error(ev.detail ?? 'sub-agent error') });
|
|
113
|
+
subSessions.delete(ev.runId);
|
|
114
|
+
return;
|
|
115
|
+
default:
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const unsub = client.on((frame) => {
|
|
120
|
+
switch (frame.type) {
|
|
121
|
+
case 'agents':
|
|
122
|
+
agents = frame.agents;
|
|
123
|
+
activeAgentId = frame.activeAgentId ?? null;
|
|
124
|
+
agentsReady.resolve();
|
|
125
|
+
return;
|
|
126
|
+
case 'commands':
|
|
127
|
+
commands = frame.commands;
|
|
128
|
+
return;
|
|
129
|
+
case 'models:listed': {
|
|
130
|
+
models = frame.models.map((m) => ({ id: m.id, ownedBy: m.ownedBy }));
|
|
131
|
+
modelSelected = frame.selected;
|
|
132
|
+
const waiters = modelWaiters.splice(0);
|
|
133
|
+
for (const w of waiters)
|
|
134
|
+
w(models);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
case 'sessions:listed': {
|
|
138
|
+
const records = frame.sessions.map((s) => ({
|
|
139
|
+
id: s.id,
|
|
140
|
+
title: s.title,
|
|
141
|
+
cwd: opts.cwd,
|
|
142
|
+
createdAt: s.createdAt,
|
|
143
|
+
}));
|
|
144
|
+
listWaiters.shift()?.(records);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
case 'sessions:raw':
|
|
148
|
+
applyRaw(frame.sessionId, frame.messages);
|
|
149
|
+
return;
|
|
150
|
+
case 'sessions:forked':
|
|
151
|
+
forkWaiters.get(frame.requestId)?.({ id: frame.sessionId, messages: frame.messages });
|
|
152
|
+
forkWaiters.delete(frame.requestId);
|
|
153
|
+
return;
|
|
154
|
+
case 'subagent:result':
|
|
155
|
+
subagentWaiters.get(frame.requestId)?.resolve({ agent: frame.agent, text: frame.text });
|
|
156
|
+
subagentWaiters.delete(frame.requestId);
|
|
157
|
+
return;
|
|
158
|
+
case 'subagent:error':
|
|
159
|
+
subagentWaiters.get(frame.requestId)?.reject(new Error(frame.message));
|
|
160
|
+
subagentWaiters.delete(frame.requestId);
|
|
161
|
+
return;
|
|
162
|
+
case 'approval_request': {
|
|
163
|
+
const req = { id: frame.requestId, name: frame.toolName, input: parseArgs(frame.args) };
|
|
164
|
+
for (const l of [...approvalListeners])
|
|
165
|
+
l(req);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
case 'sub_agent_event':
|
|
169
|
+
handleSubAgentEvent(frame.event);
|
|
170
|
+
return;
|
|
171
|
+
case 'stream':
|
|
172
|
+
route(frame.sessionId, { type: 'text', text: frame.text });
|
|
173
|
+
return;
|
|
174
|
+
case 'reasoning':
|
|
175
|
+
route(frame.sessionId, { type: 'reasoning', text: frame.text });
|
|
176
|
+
return;
|
|
177
|
+
case 'turn_start':
|
|
178
|
+
route(frame.sessionId, { type: 'turn_start', input: { role: 'user', content: [] } });
|
|
179
|
+
return;
|
|
180
|
+
case 'usage':
|
|
181
|
+
route(frame.sessionId, { type: 'usage', usage: frame.usage });
|
|
182
|
+
return;
|
|
183
|
+
case 'message':
|
|
184
|
+
for (const event of wireToEvents(frame.message))
|
|
185
|
+
route(frame.sessionId, event);
|
|
186
|
+
return;
|
|
187
|
+
case 'turn_end':
|
|
188
|
+
route(frame.sessionId, { type: 'turn_end' });
|
|
189
|
+
// Resync messages losslessly so /export + model-switch carry-over are faithful.
|
|
190
|
+
if (sessionsById.has(frame.sessionId))
|
|
191
|
+
client.send({ type: 'sessions:get', sessionId: frame.sessionId });
|
|
192
|
+
return;
|
|
193
|
+
case 'error':
|
|
194
|
+
if (frame.sessionId)
|
|
195
|
+
route(frame.sessionId, { type: 'error', error: new Error(frame.message) });
|
|
196
|
+
return;
|
|
197
|
+
default:
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
const approvals = {
|
|
202
|
+
hooks: {},
|
|
203
|
+
hooksFor: () => ({}),
|
|
204
|
+
pending: () => [],
|
|
205
|
+
resolve: (id, action) => {
|
|
206
|
+
client.send({ type: 'approval_response', requestId: id, action });
|
|
207
|
+
return true;
|
|
208
|
+
},
|
|
209
|
+
subscribe: (l) => {
|
|
210
|
+
approvalListeners.add(l);
|
|
211
|
+
return () => approvalListeners.delete(l);
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
const subAgents = {
|
|
215
|
+
register: () => { },
|
|
216
|
+
get: () => undefined,
|
|
217
|
+
list: () => [],
|
|
218
|
+
byParent: () => [],
|
|
219
|
+
subscribe: (l) => {
|
|
220
|
+
subAgentListeners.add(l);
|
|
221
|
+
return () => subAgentListeners.delete(l);
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
const agentRef = () => activeAgentId ?? agents[0]?.name ?? 'agent';
|
|
225
|
+
const requestRaw = (id) => new Promise((resolve) => {
|
|
226
|
+
rawWaiters.set(id, resolve);
|
|
227
|
+
client.send({ type: 'sessions:get', sessionId: id });
|
|
228
|
+
});
|
|
229
|
+
const host = {
|
|
230
|
+
// No initial session → ChatApp creates one lazily on the first message.
|
|
231
|
+
approvals,
|
|
232
|
+
cwd: opts.cwd,
|
|
233
|
+
createSession: () => {
|
|
234
|
+
const id = crypto.randomUUID();
|
|
235
|
+
const session = makeFakeSession(id, []);
|
|
236
|
+
sessionsById.set(id, session);
|
|
237
|
+
client.send({ type: 'sessions:create', sessionId: id });
|
|
238
|
+
return session;
|
|
239
|
+
},
|
|
240
|
+
forkSession: async (id, upToIndex) => {
|
|
241
|
+
const requestId = crypto.randomUUID();
|
|
242
|
+
const { id: newId, messages } = await new Promise((resolve) => {
|
|
243
|
+
forkWaiters.set(requestId, resolve);
|
|
244
|
+
client.send({ type: 'sessions:fork', requestId, sessionId: id, upToIndex });
|
|
245
|
+
});
|
|
246
|
+
const session = makeFakeSession(newId, messages);
|
|
247
|
+
sessionsById.set(newId, session);
|
|
248
|
+
return session;
|
|
249
|
+
},
|
|
250
|
+
listSessions: () => new Promise((resolve) => {
|
|
251
|
+
listWaiters.push(resolve);
|
|
252
|
+
client.send({ type: 'sessions:list' });
|
|
253
|
+
}),
|
|
254
|
+
openSession: async (id) => {
|
|
255
|
+
const messages = await requestRaw(id);
|
|
256
|
+
const session = makeFakeSession(id, messages);
|
|
257
|
+
sessionsById.set(id, session);
|
|
258
|
+
return session;
|
|
259
|
+
},
|
|
260
|
+
selectModel: (ref) => {
|
|
261
|
+
modelSelected = ref;
|
|
262
|
+
client.send({ type: 'models:select', ref });
|
|
263
|
+
},
|
|
264
|
+
modelRef: () => modelSelected,
|
|
265
|
+
listModels: () => new Promise((resolve) => {
|
|
266
|
+
modelWaiters.push(resolve);
|
|
267
|
+
client.send({ type: 'models:list' });
|
|
268
|
+
}),
|
|
269
|
+
agentRef,
|
|
270
|
+
agentColor: () => agents.find((a) => a.name === agentRef())?.color,
|
|
271
|
+
cycleAgent: () => agentRef(),
|
|
272
|
+
agentNames: () => agents.map((a) => a.name).filter((n) => n !== agentRef() && n !== 'title'),
|
|
273
|
+
subAgents,
|
|
274
|
+
dispatchSubAgent: (agent, task, parentId) => new Promise((resolve, reject) => {
|
|
275
|
+
const requestId = crypto.randomUUID();
|
|
276
|
+
subagentWaiters.set(requestId, { resolve, reject });
|
|
277
|
+
client.send({ type: 'subagent:dispatch', requestId, agent, task, parentId });
|
|
278
|
+
}),
|
|
279
|
+
initialTheme: opts.initialTheme ?? 'dark',
|
|
280
|
+
saveTheme: opts.saveTheme ?? (() => { }),
|
|
281
|
+
initialThinking: opts.initialThinking ?? false,
|
|
282
|
+
saveThinking: opts.saveThinking ?? (() => { }),
|
|
283
|
+
history: opts.history,
|
|
284
|
+
features: opts.features,
|
|
285
|
+
banner: opts.banner,
|
|
286
|
+
minimal: opts.minimal,
|
|
287
|
+
commands: () => commands.map((c) => ({ name: c.command.replace(/^\//, ''), description: c.description })),
|
|
288
|
+
runCommand: (input) => {
|
|
289
|
+
client.send({ type: 'command', text: input });
|
|
290
|
+
return Promise.resolve({ ok: true });
|
|
291
|
+
},
|
|
292
|
+
onExit: opts.onExit ?? (() => { }),
|
|
293
|
+
};
|
|
294
|
+
await client.connect();
|
|
295
|
+
await agentsReady.promise;
|
|
296
|
+
return {
|
|
297
|
+
host,
|
|
298
|
+
close: async () => {
|
|
299
|
+
unsub();
|
|
300
|
+
await client.close();
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * from './protocol.js';
|
|
2
|
+
export { type SessionSummaryWire, type PersistedSessionWire, type SessionService, createSessionService } from './session-service.js';
|
|
3
|
+
export { type WireMessage, messagesToWire } from './wire.js';
|
|
4
|
+
export { type WebSocketAdapter, type WebSocketAdapterOptions, webSocketAdapter } from './server.js';
|
|
5
|
+
export { type ConnectHarnessOptions, connectHarness, type RemoteHarness } from './client.js';
|
|
6
|
+
export { createWsClient, type WsClient, type WsClientOptions } from './ws-client.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * from './protocol.js';
|
|
2
|
+
export { createSessionService } from './session-service.js';
|
|
3
|
+
export { messagesToWire } from './wire.js';
|
|
4
|
+
export { webSocketAdapter } from './server.js';
|
|
5
|
+
export { connectHarness } from './client.js';
|
|
6
|
+
export { createWsClient } from './ws-client.js';
|