@borgee/agents-host 0.2.1 → 0.2.2
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/agents-host.js +0 -1
- package/dist/chat/chat-control-plane.d.ts +1 -1
- package/dist/chat/sdk-chat-control-plane.d.ts +2 -1
- package/dist/chat/sdk-chat-control-plane.js +15 -7
- package/dist/providers/claude/session-store.d.ts +23 -0
- package/dist/providers/claude/session-store.js +65 -12
- package/dist/providers/copilot/session-store.d.ts +23 -0
- package/dist/providers/copilot/session-store.js +65 -12
- package/package.json +2 -2
package/dist/agents-host.js
CHANGED
|
@@ -174,7 +174,6 @@ export class AgentsHost {
|
|
|
174
174
|
? this.enqueueProgressChannelTurn(message.channel_id, () => this.handleMessage(message))
|
|
175
175
|
: this.handleMessage(message);
|
|
176
176
|
this.trackActiveTurn(task);
|
|
177
|
-
return task;
|
|
178
177
|
});
|
|
179
178
|
const agentId = await this.ensureSelfAgentId();
|
|
180
179
|
console.log('[agents-host] connected', {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ChannelMessageEvent, MeResponseUser, PostedMessage } from '../types.js';
|
|
2
2
|
export interface ChatControlPlane {
|
|
3
|
-
connect(onMessage: (message: ChannelMessageEvent) => void
|
|
3
|
+
connect(onMessage: (message: ChannelMessageEvent) => void): Promise<void>;
|
|
4
4
|
close(): Promise<void>;
|
|
5
5
|
postMessage(channelId: string, content: string): Promise<PostedMessage>;
|
|
6
6
|
editMessage(messageId: string, content: string): Promise<void>;
|
|
@@ -11,11 +11,12 @@ type SdkChatControlPlaneOptions = Pick<BorgeePluginOptions, 'cursorStore' | 'plu
|
|
|
11
11
|
*/
|
|
12
12
|
export declare class SdkChatControlPlane implements ChatControlPlane {
|
|
13
13
|
private readonly client;
|
|
14
|
+
private pendingMessages;
|
|
14
15
|
private unsubscribe;
|
|
15
16
|
private connected;
|
|
16
17
|
private me;
|
|
17
18
|
constructor(baseUrl: string, apiKey: string, createClient?: PluginClientFactory, options?: SdkChatControlPlaneOptions);
|
|
18
|
-
connect(onMessage: (message: ChannelMessageEvent) => void
|
|
19
|
+
connect(onMessage: (message: ChannelMessageEvent) => void): Promise<void>;
|
|
19
20
|
close(): Promise<void>;
|
|
20
21
|
postMessage(channelId: string, content: string): Promise<PostedMessage>;
|
|
21
22
|
editMessage(messageId: string, content: string): Promise<void>;
|
|
@@ -6,6 +6,7 @@ import { createBorgeePlugin, } from '@borgee/plugin-sdk';
|
|
|
6
6
|
*/
|
|
7
7
|
export class SdkChatControlPlane {
|
|
8
8
|
client;
|
|
9
|
+
pendingMessages = [];
|
|
9
10
|
unsubscribe = null;
|
|
10
11
|
connected = false;
|
|
11
12
|
me = null;
|
|
@@ -13,28 +14,35 @@ export class SdkChatControlPlane {
|
|
|
13
14
|
this.client = createClient({ baseUrl, apiKey, ...options });
|
|
14
15
|
}
|
|
15
16
|
async connect(onMessage) {
|
|
16
|
-
this.unsubscribe = this.client.on('message',
|
|
17
|
-
|
|
17
|
+
this.unsubscribe = this.client.on('message', (event) => {
|
|
18
|
+
const message = mapInboundToChannelMessage(event);
|
|
19
|
+
if (!this.connected) {
|
|
20
|
+
this.pendingMessages.push(message);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
onMessage(message);
|
|
18
24
|
});
|
|
19
25
|
try {
|
|
20
|
-
// The SDK resolves connect after session.resume_ack, before every replay
|
|
21
|
-
// frame necessarily runs. Mark the consumer ready first so replay ACKs
|
|
22
|
-
// wait for the real handler instead of acknowledging an in-memory buffer.
|
|
23
|
-
this.connected = true;
|
|
24
26
|
await this.client.connect();
|
|
27
|
+
this.connected = true;
|
|
25
28
|
if (this.client.agentId) {
|
|
26
29
|
this.me = { id: this.client.agentId };
|
|
27
30
|
}
|
|
31
|
+
for (const message of this.pendingMessages) {
|
|
32
|
+
onMessage(message);
|
|
33
|
+
}
|
|
34
|
+
this.pendingMessages = [];
|
|
28
35
|
}
|
|
29
36
|
catch (error) {
|
|
30
|
-
this.connected = false;
|
|
31
37
|
this.unsubscribe?.();
|
|
32
38
|
this.unsubscribe = null;
|
|
39
|
+
this.pendingMessages = [];
|
|
33
40
|
throw error;
|
|
34
41
|
}
|
|
35
42
|
}
|
|
36
43
|
async close() {
|
|
37
44
|
this.connected = false;
|
|
45
|
+
this.pendingMessages = [];
|
|
38
46
|
this.unsubscribe?.();
|
|
39
47
|
this.unsubscribe = null;
|
|
40
48
|
await this.client.close();
|
|
@@ -4,6 +4,28 @@ export interface ClaudeChannelSessionStore {
|
|
|
4
4
|
}
|
|
5
5
|
export interface FileClaudeChannelSessionStoreOptions {
|
|
6
6
|
resolvePath(agentId: string): string;
|
|
7
|
+
fileSystem?: ClaudeSessionStoreFileSystem;
|
|
8
|
+
platform?: NodeJS.Platform;
|
|
9
|
+
}
|
|
10
|
+
interface ClaudeSessionStoreDirectoryHandle {
|
|
11
|
+
sync(): Promise<void>;
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
interface ClaudeSessionStoreFileHandle extends ClaudeSessionStoreDirectoryHandle {
|
|
15
|
+
writeFile(data: string, options: {
|
|
16
|
+
encoding: BufferEncoding;
|
|
17
|
+
}): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
interface ClaudeSessionStoreFileSystem {
|
|
20
|
+
mkdir(path: string, options: {
|
|
21
|
+
recursive: true;
|
|
22
|
+
mode: number;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
openDirectory(path: string): Promise<ClaudeSessionStoreDirectoryHandle>;
|
|
25
|
+
openFile(path: string, flags: string, mode: number): Promise<ClaudeSessionStoreFileHandle>;
|
|
26
|
+
readFile(path: string, encoding: BufferEncoding): Promise<string>;
|
|
27
|
+
rename(from: string, to: string): Promise<void>;
|
|
28
|
+
unlink(path: string): Promise<void>;
|
|
7
29
|
}
|
|
8
30
|
export declare class FileClaudeChannelSessionStore implements ClaudeChannelSessionStore {
|
|
9
31
|
private readonly options;
|
|
@@ -12,3 +34,4 @@ export declare class FileClaudeChannelSessionStore implements ClaudeChannelSessi
|
|
|
12
34
|
load(agentId: string): Promise<Record<string, string>>;
|
|
13
35
|
save(agentId: string, sessions: Record<string, string>): Promise<void>;
|
|
14
36
|
}
|
|
37
|
+
export {};
|
|
@@ -1,12 +1,63 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { mkdir, open, readFile, rename, unlink } from 'node:fs/promises';
|
|
3
3
|
import { dirname } from 'node:path';
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
const nodeFileSystem = {
|
|
5
|
+
async mkdir(path, options) {
|
|
6
|
+
await mkdir(path, options);
|
|
7
|
+
},
|
|
8
|
+
async openDirectory(path) {
|
|
9
|
+
return open(path, 'r');
|
|
10
|
+
},
|
|
11
|
+
async openFile(path, flags, mode) {
|
|
12
|
+
return open(path, flags, mode);
|
|
13
|
+
},
|
|
14
|
+
async readFile(path, encoding) {
|
|
15
|
+
return readFile(path, encoding);
|
|
16
|
+
},
|
|
17
|
+
async rename(from, to) {
|
|
18
|
+
await rename(from, to);
|
|
19
|
+
},
|
|
20
|
+
async unlink(path) {
|
|
21
|
+
await unlink(path);
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
function shouldIgnoreDirectorySyncError(platform, error) {
|
|
25
|
+
const code = error.code;
|
|
26
|
+
return platform === 'win32' && (code === 'EPERM' || code === 'EINVAL');
|
|
27
|
+
}
|
|
28
|
+
async function syncDirectory(path, fileSystem, platform) {
|
|
29
|
+
let directoryHandle;
|
|
6
30
|
try {
|
|
7
|
-
|
|
31
|
+
try {
|
|
32
|
+
directoryHandle = await fileSystem.openDirectory(path);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (!shouldIgnoreDirectorySyncError(platform, error)) {
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
await directoryHandle.sync();
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (!shouldIgnoreDirectorySyncError(platform, error)) {
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (directoryHandle) {
|
|
51
|
+
try {
|
|
52
|
+
await directoryHandle.close();
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Preserve the original directory durability failure.
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
throw error;
|
|
8
59
|
}
|
|
9
|
-
|
|
60
|
+
if (directoryHandle) {
|
|
10
61
|
await directoryHandle.close();
|
|
11
62
|
}
|
|
12
63
|
}
|
|
@@ -32,7 +83,7 @@ export class FileClaudeChannelSessionStore {
|
|
|
32
83
|
async loadFromPath(filePath) {
|
|
33
84
|
let raw;
|
|
34
85
|
try {
|
|
35
|
-
raw = await readFile(filePath, 'utf8');
|
|
86
|
+
raw = await (this.options.fileSystem ?? nodeFileSystem).readFile(filePath, 'utf8');
|
|
36
87
|
}
|
|
37
88
|
catch (error) {
|
|
38
89
|
throw error;
|
|
@@ -54,11 +105,13 @@ export class FileClaudeChannelSessionStore {
|
|
|
54
105
|
async save(agentId, sessions) {
|
|
55
106
|
const filePath = this.options.resolvePath(agentId);
|
|
56
107
|
const parentPath = dirname(filePath);
|
|
57
|
-
|
|
108
|
+
const fileSystem = this.options.fileSystem ?? nodeFileSystem;
|
|
109
|
+
const platform = this.options.platform ?? process.platform;
|
|
110
|
+
await fileSystem.mkdir(parentPath, { recursive: true, mode: 0o700 });
|
|
58
111
|
const entries = Object.entries(sessions).sort(([left], [right]) => left.localeCompare(right));
|
|
59
112
|
if (entries.length === 0) {
|
|
60
113
|
try {
|
|
61
|
-
await unlink(filePath);
|
|
114
|
+
await fileSystem.unlink(filePath);
|
|
62
115
|
}
|
|
63
116
|
catch (error) {
|
|
64
117
|
if (error.code !== 'ENOENT') {
|
|
@@ -66,21 +119,21 @@ export class FileClaudeChannelSessionStore {
|
|
|
66
119
|
}
|
|
67
120
|
return;
|
|
68
121
|
}
|
|
69
|
-
await syncDirectory(parentPath);
|
|
122
|
+
await syncDirectory(parentPath, fileSystem, platform);
|
|
70
123
|
return;
|
|
71
124
|
}
|
|
72
125
|
const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
73
126
|
let temporaryHandle;
|
|
74
127
|
try {
|
|
75
|
-
temporaryHandle = await
|
|
128
|
+
temporaryHandle = await fileSystem.openFile(temporaryPath, 'wx', 0o600);
|
|
76
129
|
await temporaryHandle.writeFile(JSON.stringify(Object.fromEntries(entries)), {
|
|
77
130
|
encoding: 'utf8',
|
|
78
131
|
});
|
|
79
132
|
await temporaryHandle.sync();
|
|
80
133
|
await temporaryHandle.close();
|
|
81
134
|
temporaryHandle = undefined;
|
|
82
|
-
await rename(temporaryPath, filePath);
|
|
83
|
-
await syncDirectory(parentPath);
|
|
135
|
+
await fileSystem.rename(temporaryPath, filePath);
|
|
136
|
+
await syncDirectory(parentPath, fileSystem, platform);
|
|
84
137
|
}
|
|
85
138
|
catch (error) {
|
|
86
139
|
if (temporaryHandle) {
|
|
@@ -92,7 +145,7 @@ export class FileClaudeChannelSessionStore {
|
|
|
92
145
|
}
|
|
93
146
|
}
|
|
94
147
|
try {
|
|
95
|
-
await unlink(temporaryPath);
|
|
148
|
+
await fileSystem.unlink(temporaryPath);
|
|
96
149
|
}
|
|
97
150
|
catch {
|
|
98
151
|
// Cleanup is best effort.
|
|
@@ -4,6 +4,28 @@ export interface CopilotChannelSessionStore {
|
|
|
4
4
|
}
|
|
5
5
|
export interface FileCopilotChannelSessionStoreOptions {
|
|
6
6
|
resolvePath(agentId: string): string;
|
|
7
|
+
fileSystem?: CopilotSessionStoreFileSystem;
|
|
8
|
+
platform?: NodeJS.Platform;
|
|
9
|
+
}
|
|
10
|
+
interface CopilotSessionStoreDirectoryHandle {
|
|
11
|
+
sync(): Promise<void>;
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
interface CopilotSessionStoreFileHandle extends CopilotSessionStoreDirectoryHandle {
|
|
15
|
+
writeFile(data: string, options: {
|
|
16
|
+
encoding: BufferEncoding;
|
|
17
|
+
}): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
interface CopilotSessionStoreFileSystem {
|
|
20
|
+
mkdir(path: string, options: {
|
|
21
|
+
recursive: true;
|
|
22
|
+
mode: number;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
openDirectory(path: string): Promise<CopilotSessionStoreDirectoryHandle>;
|
|
25
|
+
openFile(path: string, flags: string, mode: number): Promise<CopilotSessionStoreFileHandle>;
|
|
26
|
+
readFile(path: string, encoding: BufferEncoding): Promise<string>;
|
|
27
|
+
rename(from: string, to: string): Promise<void>;
|
|
28
|
+
unlink(path: string): Promise<void>;
|
|
7
29
|
}
|
|
8
30
|
export declare class FileCopilotChannelSessionStore implements CopilotChannelSessionStore {
|
|
9
31
|
private readonly options;
|
|
@@ -12,3 +34,4 @@ export declare class FileCopilotChannelSessionStore implements CopilotChannelSes
|
|
|
12
34
|
load(agentId: string): Promise<Record<string, string>>;
|
|
13
35
|
save(agentId: string, sessions: Record<string, string>): Promise<void>;
|
|
14
36
|
}
|
|
37
|
+
export {};
|
|
@@ -1,12 +1,63 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { mkdir, open, readFile, rename, unlink } from 'node:fs/promises';
|
|
3
3
|
import { dirname } from 'node:path';
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
const nodeFileSystem = {
|
|
5
|
+
async mkdir(path, options) {
|
|
6
|
+
await mkdir(path, options);
|
|
7
|
+
},
|
|
8
|
+
async openDirectory(path) {
|
|
9
|
+
return open(path, 'r');
|
|
10
|
+
},
|
|
11
|
+
async openFile(path, flags, mode) {
|
|
12
|
+
return open(path, flags, mode);
|
|
13
|
+
},
|
|
14
|
+
async readFile(path, encoding) {
|
|
15
|
+
return readFile(path, encoding);
|
|
16
|
+
},
|
|
17
|
+
async rename(from, to) {
|
|
18
|
+
await rename(from, to);
|
|
19
|
+
},
|
|
20
|
+
async unlink(path) {
|
|
21
|
+
await unlink(path);
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
function shouldIgnoreDirectorySyncError(platform, error) {
|
|
25
|
+
const code = error.code;
|
|
26
|
+
return platform === 'win32' && (code === 'EPERM' || code === 'EINVAL');
|
|
27
|
+
}
|
|
28
|
+
async function syncDirectory(path, fileSystem, platform) {
|
|
29
|
+
let directoryHandle;
|
|
6
30
|
try {
|
|
7
|
-
|
|
31
|
+
try {
|
|
32
|
+
directoryHandle = await fileSystem.openDirectory(path);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (!shouldIgnoreDirectorySyncError(platform, error)) {
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
await directoryHandle.sync();
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (!shouldIgnoreDirectorySyncError(platform, error)) {
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (directoryHandle) {
|
|
51
|
+
try {
|
|
52
|
+
await directoryHandle.close();
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Preserve the original directory durability failure.
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
throw error;
|
|
8
59
|
}
|
|
9
|
-
|
|
60
|
+
if (directoryHandle) {
|
|
10
61
|
await directoryHandle.close();
|
|
11
62
|
}
|
|
12
63
|
}
|
|
@@ -30,7 +81,7 @@ export class FileCopilotChannelSessionStore {
|
|
|
30
81
|
this.options = options;
|
|
31
82
|
}
|
|
32
83
|
async loadFromPath(filePath) {
|
|
33
|
-
const raw = await readFile(filePath, 'utf8');
|
|
84
|
+
const raw = await (this.options.fileSystem ?? nodeFileSystem).readFile(filePath, 'utf8');
|
|
34
85
|
return normalizePersistedSessions(JSON.parse(raw), filePath);
|
|
35
86
|
}
|
|
36
87
|
async load(agentId) {
|
|
@@ -48,11 +99,13 @@ export class FileCopilotChannelSessionStore {
|
|
|
48
99
|
async save(agentId, sessions) {
|
|
49
100
|
const filePath = this.options.resolvePath(agentId);
|
|
50
101
|
const parentPath = dirname(filePath);
|
|
51
|
-
|
|
102
|
+
const fileSystem = this.options.fileSystem ?? nodeFileSystem;
|
|
103
|
+
const platform = this.options.platform ?? process.platform;
|
|
104
|
+
await fileSystem.mkdir(parentPath, { recursive: true, mode: 0o700 });
|
|
52
105
|
const entries = Object.entries(sessions).sort(([left], [right]) => left.localeCompare(right));
|
|
53
106
|
if (entries.length === 0) {
|
|
54
107
|
try {
|
|
55
|
-
await unlink(filePath);
|
|
108
|
+
await fileSystem.unlink(filePath);
|
|
56
109
|
}
|
|
57
110
|
catch (error) {
|
|
58
111
|
if (error.code !== 'ENOENT') {
|
|
@@ -60,21 +113,21 @@ export class FileCopilotChannelSessionStore {
|
|
|
60
113
|
}
|
|
61
114
|
return;
|
|
62
115
|
}
|
|
63
|
-
await syncDirectory(parentPath);
|
|
116
|
+
await syncDirectory(parentPath, fileSystem, platform);
|
|
64
117
|
return;
|
|
65
118
|
}
|
|
66
119
|
const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
67
120
|
let temporaryHandle;
|
|
68
121
|
try {
|
|
69
|
-
temporaryHandle = await
|
|
122
|
+
temporaryHandle = await fileSystem.openFile(temporaryPath, 'wx', 0o600);
|
|
70
123
|
await temporaryHandle.writeFile(JSON.stringify(Object.fromEntries(entries)), {
|
|
71
124
|
encoding: 'utf8',
|
|
72
125
|
});
|
|
73
126
|
await temporaryHandle.sync();
|
|
74
127
|
await temporaryHandle.close();
|
|
75
128
|
temporaryHandle = undefined;
|
|
76
|
-
await rename(temporaryPath, filePath);
|
|
77
|
-
await syncDirectory(parentPath);
|
|
129
|
+
await fileSystem.rename(temporaryPath, filePath);
|
|
130
|
+
await syncDirectory(parentPath, fileSystem, platform);
|
|
78
131
|
}
|
|
79
132
|
catch (error) {
|
|
80
133
|
if (temporaryHandle) {
|
|
@@ -86,7 +139,7 @@ export class FileCopilotChannelSessionStore {
|
|
|
86
139
|
}
|
|
87
140
|
}
|
|
88
141
|
try {
|
|
89
|
-
await unlink(temporaryPath);
|
|
142
|
+
await fileSystem.unlink(temporaryPath);
|
|
90
143
|
}
|
|
91
144
|
catch {
|
|
92
145
|
// Cleanup is best effort.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@borgee/agents-host",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@agentclientprotocol/sdk": "^1.2.1",
|
|
39
|
-
"@borgee/plugin-sdk": "0.1.
|
|
39
|
+
"@borgee/plugin-sdk": "0.1.5",
|
|
40
40
|
"cross-spawn": "^7.0.6",
|
|
41
41
|
"yaml": "^2.8.1"
|
|
42
42
|
},
|