@agents-fleet/shared 0.7.0-rc.1
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 +9 -0
- package/settingsMerge.ts +53 -0
- package/tsconfig.json +30 -0
- package/utils/sanitizeFilePath.ts +13 -0
- package/wsTypes.ts +242 -0
package/package.json
ADDED
package/settingsMerge.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const MAX_MERGE_DEPTH = 50;
|
|
2
|
+
|
|
3
|
+
const PERMISSION_ARRAY_KEYS = new Set(["allow", "ask", "deny"]);
|
|
4
|
+
|
|
5
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
6
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Safe parse of user-supplied settings JSON text; returns `{}` on empty/invalid/non-object input. */
|
|
10
|
+
export function parseSettingsJson(text: string): Record<string, unknown> {
|
|
11
|
+
if (!text || !text.trim()) return {};
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const parsed: unknown = JSON.parse(text);
|
|
15
|
+
return isPlainObject(parsed) ? parsed : {};
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function mergeValue(baseValue: unknown, overrideValue: unknown, key: string, depth: number, inPermissions: boolean): unknown {
|
|
23
|
+
if (depth >= MAX_MERGE_DEPTH) return overrideValue;
|
|
24
|
+
|
|
25
|
+
if (inPermissions && PERMISSION_ARRAY_KEYS.has(key) && Array.isArray(baseValue) && Array.isArray(overrideValue)) {
|
|
26
|
+
return [...new Set([...baseValue, ...overrideValue])];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (isPlainObject(baseValue) && isPlainObject(overrideValue)) {
|
|
30
|
+
return mergeObjects(baseValue, overrideValue, depth + 1, key === "permissions" || inPermissions);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return overrideValue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function mergeObjects(base: Record<string, unknown>, override: Record<string, unknown>, depth: number, inPermissions: boolean): Record<string, unknown> {
|
|
37
|
+
const result: Record<string, unknown> = { ...base };
|
|
38
|
+
|
|
39
|
+
for (const key of Object.keys(override)) {
|
|
40
|
+
result[key] = key in base ? mergeValue(base[key], override[key], key, depth, inPermissions) : override[key];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Deep-merges `override` on top of `base`, up to `MAX_MERGE_DEPTH` levels. Plain-object values
|
|
48
|
+
* present on both sides are merged recursively; everything else (including arrays) is replaced
|
|
49
|
+
* by `override`'s value, except `permissions.allow/ask/deny`, which are unioned+deduped.
|
|
50
|
+
*/
|
|
51
|
+
export function mergeSettings(base: Record<string, unknown>, override: Record<string, unknown>): Record<string, unknown> {
|
|
52
|
+
return mergeObjects(base, override, 0, false);
|
|
53
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Environment setup & latest features
|
|
4
|
+
"lib": ["ESNext"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "Preserve",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
"types": ["bun"],
|
|
11
|
+
|
|
12
|
+
// Bundler mode
|
|
13
|
+
"moduleResolution": "bundler",
|
|
14
|
+
"allowImportingTsExtensions": true,
|
|
15
|
+
"verbatimModuleSyntax": true,
|
|
16
|
+
"noEmit": true,
|
|
17
|
+
|
|
18
|
+
// Best practices
|
|
19
|
+
"strict": true,
|
|
20
|
+
"skipLibCheck": true,
|
|
21
|
+
"noFallthroughCasesInSwitch": true,
|
|
22
|
+
"noUncheckedIndexedAccess": true,
|
|
23
|
+
"noImplicitOverride": true,
|
|
24
|
+
|
|
25
|
+
// Some stricter flags (disabled by default)
|
|
26
|
+
"noUnusedLocals": false,
|
|
27
|
+
"noUnusedParameters": false,
|
|
28
|
+
"noPropertyAccessFromIndexSignature": false
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { sep } from "path";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Strips `.`/`..` segments (and empty segments from absolute paths or repeated
|
|
5
|
+
* slashes) from `relPath` so the result can never climb above the directory
|
|
6
|
+
* it's later joined to.
|
|
7
|
+
*/
|
|
8
|
+
export function sanitizeFilePath(relPath: string): string {
|
|
9
|
+
return relPath
|
|
10
|
+
.split(/[/\\]+/)
|
|
11
|
+
.filter(segment => segment !== "" && segment !== "." && segment !== "..")
|
|
12
|
+
.join(sep);
|
|
13
|
+
}
|
package/wsTypes.ts
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
export type ClientMessage = {
|
|
2
|
+
id: string;
|
|
3
|
+
originId: string;
|
|
4
|
+
sender: string;
|
|
5
|
+
/** Human-readable display name for `sender`, when the client can resolve one (e.g. Slack real/display name). */
|
|
6
|
+
senderName?: string;
|
|
7
|
+
message: string;
|
|
8
|
+
attachments?: IAttachment[];
|
|
9
|
+
/** Other messages the user linked to (e.g. a Slack message permalink), resolved for context. */
|
|
10
|
+
linkedMessages?: ClientMessage[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export interface IAttachment {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
mimetype: string;
|
|
17
|
+
/** Base64-encoded file bytes, or null if the download failed */
|
|
18
|
+
data: string | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type UserQuestion = {
|
|
22
|
+
question: string;
|
|
23
|
+
header: string;
|
|
24
|
+
options: {
|
|
25
|
+
label: string;
|
|
26
|
+
description: string;
|
|
27
|
+
preview?: string;
|
|
28
|
+
}[];
|
|
29
|
+
multiSelect: boolean;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type UserAnswers = {
|
|
33
|
+
questionId: string;
|
|
34
|
+
answers: Record<string, string | string[]>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type ISkillFile = {
|
|
38
|
+
/** Path relative to the skill's root inside the zip, e.g. "SKILL.md", "scripts/dosth.py" */
|
|
39
|
+
path: string;
|
|
40
|
+
type: "text" | "binary";
|
|
41
|
+
/** Plain text if type is "text", base64-encoded bytes if type is "binary" */
|
|
42
|
+
contents: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** Short-lived credential the worker uses to authenticate `git` CLI operations directly against GitHub, separate from the `github` MCP server (which only exposes issues/PR tools) */
|
|
46
|
+
export type GitCredentials = {
|
|
47
|
+
token: string;
|
|
48
|
+
expiresAt: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type McpServerConfig = {
|
|
52
|
+
id: string;
|
|
53
|
+
name: string;
|
|
54
|
+
} & (
|
|
55
|
+
| {
|
|
56
|
+
transport: "http";
|
|
57
|
+
url: string;
|
|
58
|
+
headers?: Record<string, string>;
|
|
59
|
+
}
|
|
60
|
+
| {
|
|
61
|
+
transport: "stdio";
|
|
62
|
+
command: string;
|
|
63
|
+
args?: string[];
|
|
64
|
+
env?: Record<string, string>;
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
export type WireGithubRepo = {
|
|
69
|
+
fullName: string;
|
|
70
|
+
/** Free-text context about the repo, set by users via manager-web, surfaced to the worker's system prompt */
|
|
71
|
+
description?: string;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export type TaskStatus = "pending" | "in_progress" | "completed" | "deleted";
|
|
75
|
+
|
|
76
|
+
export type WorkerMessage = {
|
|
77
|
+
/** Unique ID for this message, used to match the "ack" reply */
|
|
78
|
+
id: string;
|
|
79
|
+
type: string;
|
|
80
|
+
payload: unknown;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export type FromWorkerMessage = FromWorker_MessageAck | FromWorker_NewAssistantMessage | FromWorker_AskUserQuestions | FromWorker_ToolCallRequest | FromWorker_UpdateMemory | FromWorker_TaskCreate | FromWorker_TaskUpdate | FromWorker_ClearContext | FromWorker_ExposePortRequest;
|
|
84
|
+
|
|
85
|
+
/** Sent by the worker back to the manager to acknowledge receipt of a message */
|
|
86
|
+
export type FromWorker_MessageAck = WorkerMessage & {
|
|
87
|
+
type: "ack-client-message";
|
|
88
|
+
payload: {
|
|
89
|
+
/** ID of the message being acknowledged */
|
|
90
|
+
id: string;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export type FromWorker_NewAssistantMessage = WorkerMessage & {
|
|
95
|
+
type: "new-assistant-message";
|
|
96
|
+
payload: {
|
|
97
|
+
message: string;
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export type FromWorker_AskUserQuestions = WorkerMessage & {
|
|
102
|
+
type: "ask-user-questions";
|
|
103
|
+
payload: {
|
|
104
|
+
id: string;
|
|
105
|
+
questions: UserQuestion[];
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export type FromWorker_ToolCallRequest = WorkerMessage & {
|
|
110
|
+
type: "tool-call-request";
|
|
111
|
+
payload: {
|
|
112
|
+
id: string;
|
|
113
|
+
toolName: string;
|
|
114
|
+
description: string;
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export type FromWorker_UpdateMemory = WorkerMessage & {
|
|
119
|
+
type: "update-memory";
|
|
120
|
+
payload: {
|
|
121
|
+
prevMemory: string;
|
|
122
|
+
memory: string;
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export type FromWorker_TaskCreate = WorkerMessage & {
|
|
127
|
+
type: "task-create";
|
|
128
|
+
payload: {
|
|
129
|
+
taskId: string;
|
|
130
|
+
subject: string;
|
|
131
|
+
description?: string;
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export type FromWorker_TaskUpdate = WorkerMessage & {
|
|
136
|
+
type: "task-update";
|
|
137
|
+
payload: {
|
|
138
|
+
taskId: string;
|
|
139
|
+
status?: TaskStatus;
|
|
140
|
+
subject?: string;
|
|
141
|
+
description?: string;
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/** Sent by the worker when its session is reset via `/clear`, so the manager forgets the last-read message cursor for this conversation and resends full context next time. */
|
|
146
|
+
export type FromWorker_ClearContext = WorkerMessage & {
|
|
147
|
+
type: "clear-context";
|
|
148
|
+
payload: Record<string, never>;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/** Sent by the worker to ask the manager to reverse-proxy a public URL to a port the worker is listening on. */
|
|
152
|
+
export type FromWorker_ExposePortRequest = WorkerMessage & {
|
|
153
|
+
type: "expose-port-request";
|
|
154
|
+
payload: {
|
|
155
|
+
id: string;
|
|
156
|
+
port: number;
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export type ToWorkerMessage = ToWorker_NewClientMessage | ToWorker_AnswerQuestions | ToWorker_ToolCallReply | ToWorker_StartWorker | ToWorker_UpdateRuntimeConfigs | ToWorker_UpdateMemoryResult | ToWorker_ExposePortReply;
|
|
161
|
+
|
|
162
|
+
export type ToWorker_NewClientMessage = WorkerMessage & {
|
|
163
|
+
type: "new-client-message";
|
|
164
|
+
payload: {
|
|
165
|
+
message: ClientMessage;
|
|
166
|
+
previousMessages?: ClientMessage[];
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export type ToWorker_AnswerQuestions = WorkerMessage & {
|
|
171
|
+
type: "answer-questions";
|
|
172
|
+
payload: {
|
|
173
|
+
questionId: string;
|
|
174
|
+
questions: string[];
|
|
175
|
+
/** Question as the key and value as `string[]` if the question accepts multiple answers or `string` if the question accepts a single answer */
|
|
176
|
+
answers: Record<string, string | string[]>;
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export type ToWorker_ToolCallReply = WorkerMessage & {
|
|
181
|
+
type: "tool-call-reply";
|
|
182
|
+
payload: {
|
|
183
|
+
id: string;
|
|
184
|
+
action: "allow" | "deny";
|
|
185
|
+
message?: string;
|
|
186
|
+
};
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
export type ToWorker_StartWorker = WorkerMessage & {
|
|
190
|
+
type: "start-worker";
|
|
191
|
+
payload: {
|
|
192
|
+
globalSystemPrompt: string;
|
|
193
|
+
channelSystemPrompt: string;
|
|
194
|
+
memory: string;
|
|
195
|
+
settingsJson: string;
|
|
196
|
+
mcpServers: McpServerConfig[];
|
|
197
|
+
git?: GitCredentials;
|
|
198
|
+
githubRepos: WireGithubRepo[];
|
|
199
|
+
githubReadOnly: boolean;
|
|
200
|
+
skills: Array<{
|
|
201
|
+
id: string;
|
|
202
|
+
name: string;
|
|
203
|
+
description: string | null;
|
|
204
|
+
updatedAt: string;
|
|
205
|
+
files: ISkillFile[];
|
|
206
|
+
}>;
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/** Sent by the manager to push refreshed runtime config (MCP servers, e.g. renewed GitHub App tokens; settings) to a running worker without restarting it */
|
|
211
|
+
export type ToWorker_UpdateRuntimeConfigs = WorkerMessage & {
|
|
212
|
+
type: "update-runtime-configs";
|
|
213
|
+
payload: {
|
|
214
|
+
mcpServers: McpServerConfig[];
|
|
215
|
+
git?: GitCredentials;
|
|
216
|
+
githubRepos: WireGithubRepo[];
|
|
217
|
+
githubReadOnly: boolean;
|
|
218
|
+
settingsJson: string;
|
|
219
|
+
};
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/** Reply to a worker's `update-memory` message. `requestId` matches the `id` of the `update-memory` message being answered. */
|
|
223
|
+
export type ToWorker_UpdateMemoryResult = WorkerMessage & {
|
|
224
|
+
type: "update-memory-result";
|
|
225
|
+
payload: {
|
|
226
|
+
requestId: string;
|
|
227
|
+
success: boolean;
|
|
228
|
+
/** Current memory as stored in the DB - the canonical value to use as `prevMemory` for the next update, whether this one succeeded or was rejected */
|
|
229
|
+
memory: string;
|
|
230
|
+
};
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/** Reply to a worker's `expose-port-request` message. `requestId` matches the `id` of the `expose-port-request` message being answered. */
|
|
234
|
+
export type ToWorker_ExposePortReply = WorkerMessage & {
|
|
235
|
+
type: "expose-port-reply";
|
|
236
|
+
payload: {
|
|
237
|
+
requestId: string;
|
|
238
|
+
success: boolean;
|
|
239
|
+
url?: string;
|
|
240
|
+
error?: string;
|
|
241
|
+
};
|
|
242
|
+
};
|