pipeline-moe 0.1.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.
Potentially problematic release.
This version of pipeline-moe might be problematic. Click here for more details.
- package/.env.example +26 -0
- package/LICENSE +21 -0
- package/README.md +408 -0
- package/bin/pipeline-moe.mjs +40 -0
- package/package.json +68 -0
- package/presets/2106BUILD.json +163 -0
- package/presets/CHEAPBUILD.json +97 -0
- package/presets/FREEROOM.json +96 -0
- package/presets/Versa.json +145 -0
- package/presets/cloud-main.json +76 -0
- package/presets/cloud-sprint.json +70 -0
- package/presets/local-default.json +152 -0
- package/presets/main.json +147 -0
- package/presets/mainmix.json +145 -0
- package/src/circuit-breaker.ts +141 -0
- package/src/config.ts +46 -0
- package/src/custom-tools/arxiv-search.ts +186 -0
- package/src/custom-tools/check-room.ts +45 -0
- package/src/custom-tools/destroy-room.ts +45 -0
- package/src/custom-tools/index.ts +75 -0
- package/src/custom-tools/spawn-room.ts +99 -0
- package/src/custom-tools/stop-room.ts +50 -0
- package/src/custom-tools/web-read.ts +104 -0
- package/src/custom-tools/web-search.ts +144 -0
- package/src/custom-tools/youcom-search.ts +230 -0
- package/src/custom-tools/youtube-transcript.ts +124 -0
- package/src/local-model-lock.ts +52 -0
- package/src/model.ts +131 -0
- package/src/orchestrator.ts +59 -0
- package/src/participant.ts +423 -0
- package/src/path-guard.ts +13 -0
- package/src/personas.ts +480 -0
- package/src/receipts.ts +120 -0
- package/src/registry.ts +317 -0
- package/src/room-manager.ts +505 -0
- package/src/room.ts +1657 -0
- package/src/sandbox-tools.ts +141 -0
- package/src/server.ts +1846 -0
- package/src/sse.ts +86 -0
- package/src/sshfs.ts +139 -0
- package/src/store.ts +79 -0
- package/src/types.ts +131 -0
- package/src/validation.ts +36 -0
- package/tsconfig.json +15 -0
- package/web/dist/assets/index-CmMGhMKG.css +1 -0
- package/web/dist/assets/index-LAGqbZII.js +41 -0
- package/web/dist/index.html +13 -0
- package/web/tsconfig.json +21 -0
package/src/registry.ts
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
// The participant registry: the live roster of agents. Create / activate /
|
|
2
|
+
// deactivate / kick all happen here. Emits a "roster" SSE event on any change.
|
|
3
|
+
|
|
4
|
+
import { config } from "./config.js"
|
|
5
|
+
import { isAllowedModel as isAllowedModel_ } from "./model.js"
|
|
6
|
+
import { Participant } from "./participant.js"
|
|
7
|
+
import type { ResolvedModel } from "./model.js"
|
|
8
|
+
import type { RoomOrchestrator } from "./orchestrator.js"
|
|
9
|
+
import type { SseHub } from "./sse.js"
|
|
10
|
+
import type { Persona, PersonaState } from "./types.js"
|
|
11
|
+
|
|
12
|
+
export interface RosterItem {
|
|
13
|
+
id: string
|
|
14
|
+
name: string
|
|
15
|
+
color: string
|
|
16
|
+
icon: string
|
|
17
|
+
tools: string[]
|
|
18
|
+
active: boolean
|
|
19
|
+
status: string
|
|
20
|
+
/** Per-agent model "provider/id", or undefined when on the default. */
|
|
21
|
+
model?: string
|
|
22
|
+
/** Per-agent thinking level, or undefined when inheriting from global config. */
|
|
23
|
+
thinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh"
|
|
24
|
+
/** May run concurrently with adjacent parallel-flagged agents. */
|
|
25
|
+
parallel: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class Registry {
|
|
29
|
+
private participants = new Map<string, Participant>()
|
|
30
|
+
/** Fired after any roster mutation (create/activate/kick). Used for autosave. */
|
|
31
|
+
onChange: (() => void) | null = null
|
|
32
|
+
|
|
33
|
+
constructor(
|
|
34
|
+
private readonly resolved: ResolvedModel,
|
|
35
|
+
private readonly hub: SseHub,
|
|
36
|
+
/** Providers the user has explicitly enabled via /api/providers. */
|
|
37
|
+
private readonly explicitlyEnabledProviders: Set<string> = new Set(),
|
|
38
|
+
/** Directory each participant's file tools are confined to. Defaults to the
|
|
39
|
+
* pipeline workspace; per-room scopes override it. */
|
|
40
|
+
private readonly workspaceDir: string = config.workspaceDir,
|
|
41
|
+
/** Logical room this registry belongs to. Tags roster + participant SSE
|
|
42
|
+
* events so room-filtered clients don't receive another room's roster. */
|
|
43
|
+
private readonly roomId: string = "default",
|
|
44
|
+
/** Capability surface for spawning sub-rooms. Passed to each participant so
|
|
45
|
+
* orchestrator personas (the planner) get spawn/check/destroy_room tools.
|
|
46
|
+
* Undefined in tests and before the server wires it up. */
|
|
47
|
+
private readonly orchestrator?: RoomOrchestrator,
|
|
48
|
+
) {}
|
|
49
|
+
|
|
50
|
+
/** Default thinking level for new participants without a per-agent override.
|
|
51
|
+
* Mutable — the Room can change it at runtime and existing participants
|
|
52
|
+
* keep their current level; only new participants pick up the change. */
|
|
53
|
+
private defaultThinkingLevel: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" = config.thinkingLevel
|
|
54
|
+
|
|
55
|
+
setDefaultThinkingLevel(level: "off" | "minimal" | "low" | "medium" | "high" | "xhigh"): void {
|
|
56
|
+
this.defaultThinkingLevel = level
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
getDefaultThinkingLevel(): "off" | "minimal" | "low" | "medium" | "high" | "xhigh" {
|
|
60
|
+
return this.defaultThinkingLevel
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Whether cloud models are allowed in this room. Mutable — the Room can
|
|
64
|
+
* change it at runtime; only new participants pick up the change. */
|
|
65
|
+
private allowCloud: boolean = config.allowCloud
|
|
66
|
+
|
|
67
|
+
setAllowCloud(value: boolean): void {
|
|
68
|
+
this.allowCloud = value
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
getAllowCloud(): boolean {
|
|
72
|
+
return this.allowCloud
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Reserve tokens for auto-compaction. Mutable — the Room can change it at
|
|
76
|
+
* runtime; only new participants pick up the change. */
|
|
77
|
+
private compactionReserveTokens: number = 38000
|
|
78
|
+
|
|
79
|
+
setCompactionReserveTokens(value: number): void {
|
|
80
|
+
this.compactionReserveTokens = value
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
getCompactionReserveTokens(): number {
|
|
84
|
+
return this.compactionReserveTokens
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Participants that should take part in the loop, in insertion order. */
|
|
88
|
+
activeParticipants(): Participant[] {
|
|
89
|
+
return [...this.participants.values()].filter((p) => p.active)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Snapshot of the roster as personas + runtime flags, for persistence. */
|
|
93
|
+
personaStates(): PersonaState[] {
|
|
94
|
+
return [...this.participants.values()].map((p) => ({
|
|
95
|
+
...p.persona,
|
|
96
|
+
active: p.active,
|
|
97
|
+
parallel: p.parallel,
|
|
98
|
+
}))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get(id: string): Participant | undefined {
|
|
102
|
+
return this.participants.get(id)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
has(id: string): boolean {
|
|
106
|
+
return this.participants.has(id)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
roster(): RosterItem[] {
|
|
110
|
+
return [...this.participants.values()].map((p) => ({
|
|
111
|
+
id: p.persona.id,
|
|
112
|
+
name: p.persona.name,
|
|
113
|
+
color: p.persona.color,
|
|
114
|
+
icon: p.persona.icon,
|
|
115
|
+
tools: p.persona.tools,
|
|
116
|
+
active: p.active,
|
|
117
|
+
status: p.status,
|
|
118
|
+
model: p.persona.model,
|
|
119
|
+
thinkingLevel: p.persona.thinkingLevel,
|
|
120
|
+
parallel: p.parallel,
|
|
121
|
+
}))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
broadcastRoster(): void {
|
|
125
|
+
this.hub.broadcast("roster", this.roster(), this.roomId)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async create(persona: Persona, active = true, parallel = false): Promise<Participant> {
|
|
129
|
+
if (this.participants.has(persona.id)) {
|
|
130
|
+
throw new Error(`participant "${persona.id}" already exists`)
|
|
131
|
+
}
|
|
132
|
+
console.log(`[Registry.create] persona=${persona.id} has orchestrator=${!!this.orchestrator}`)
|
|
133
|
+
const participant = await Participant.create(
|
|
134
|
+
persona,
|
|
135
|
+
this.resolved,
|
|
136
|
+
(event, data) => this.hub.broadcast(event, data, this.roomId),
|
|
137
|
+
this.workspaceDir,
|
|
138
|
+
this.orchestrator,
|
|
139
|
+
this.defaultThinkingLevel,
|
|
140
|
+
this.allowCloud,
|
|
141
|
+
this.compactionReserveTokens,
|
|
142
|
+
)
|
|
143
|
+
// Catch a new participant up on everything said so far before its first turn.
|
|
144
|
+
participant.cursor = 0
|
|
145
|
+
participant.active = active
|
|
146
|
+
participant.parallel = parallel
|
|
147
|
+
this.participants.set(persona.id, participant)
|
|
148
|
+
this.broadcastRoster()
|
|
149
|
+
this.onChange?.()
|
|
150
|
+
return participant
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Editable persona fields (id is immutable — it is the @mention handle). */
|
|
154
|
+
async update(
|
|
155
|
+
id: string,
|
|
156
|
+
patch: Partial<Pick<Persona, "name" | "color" | "icon" | "tools" | "systemPrompt" | "model" | "thinkingLevel">>,
|
|
157
|
+
): Promise<Participant> {
|
|
158
|
+
const existing = this.participants.get(id)
|
|
159
|
+
if (!existing) throw new Error(`unknown participant "${id}"`)
|
|
160
|
+
const order = [...this.participants.keys()]
|
|
161
|
+
const wasActive = existing.active
|
|
162
|
+
const persona: Persona = { ...existing.persona, ...patch, id }
|
|
163
|
+
|
|
164
|
+
// A new system prompt = a new identity, so we rebuild the pi session now
|
|
165
|
+
// rather than mutating the running one. cursor=0 → it replays the room
|
|
166
|
+
// transcript on its next turn with the new persona.
|
|
167
|
+
existing.dispose()
|
|
168
|
+
const replacement = await Participant.create(
|
|
169
|
+
persona,
|
|
170
|
+
this.resolved,
|
|
171
|
+
(event, data) => this.hub.broadcast(event, data, this.roomId),
|
|
172
|
+
this.workspaceDir,
|
|
173
|
+
this.orchestrator,
|
|
174
|
+
this.defaultThinkingLevel,
|
|
175
|
+
this.allowCloud,
|
|
176
|
+
this.compactionReserveTokens,
|
|
177
|
+
)
|
|
178
|
+
replacement.cursor = 0
|
|
179
|
+
replacement.active = wasActive
|
|
180
|
+
|
|
181
|
+
// Rebuild the Map in the original order (a plain set() would move it last,
|
|
182
|
+
// reordering @all / first-active routing).
|
|
183
|
+
const rebuilt = new Map<string, Participant>()
|
|
184
|
+
for (const key of order) {
|
|
185
|
+
rebuilt.set(key, key === id ? replacement : this.participants.get(key)!)
|
|
186
|
+
}
|
|
187
|
+
this.participants = rebuilt
|
|
188
|
+
|
|
189
|
+
this.broadcastRoster()
|
|
190
|
+
this.onChange?.()
|
|
191
|
+
return replacement
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Reorder the roster to match the given id sequence. This is the first-turn /
|
|
195
|
+
* @all execution order (and the first-active fallback default). Ids not listed
|
|
196
|
+
* keep their relative order, appended after the listed ones. No session is
|
|
197
|
+
* recreated — only references move — so it is safe to call mid-turn. */
|
|
198
|
+
reorder(orderedIds: string[]): void {
|
|
199
|
+
const seen = new Set<string>()
|
|
200
|
+
const next: string[] = []
|
|
201
|
+
for (const id of orderedIds) {
|
|
202
|
+
if (this.participants.has(id) && !seen.has(id)) {
|
|
203
|
+
next.push(id)
|
|
204
|
+
seen.add(id)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
for (const id of this.participants.keys()) if (!seen.has(id)) next.push(id)
|
|
208
|
+
|
|
209
|
+
const rebuilt = new Map<string, Participant>()
|
|
210
|
+
for (const id of next) rebuilt.set(id, this.participants.get(id)!)
|
|
211
|
+
this.participants = rebuilt
|
|
212
|
+
|
|
213
|
+
this.broadcastRoster()
|
|
214
|
+
this.onChange?.()
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
setActive(id: string, active: boolean): Participant {
|
|
218
|
+
const p = this.participants.get(id)
|
|
219
|
+
if (!p) throw new Error(`unknown participant "${id}"`)
|
|
220
|
+
p.active = active
|
|
221
|
+
p.status = "idle"
|
|
222
|
+
this.broadcastRoster()
|
|
223
|
+
this.onChange?.()
|
|
224
|
+
return p
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Toggle whether an agent may run concurrently. Pure runtime flag — no
|
|
228
|
+
* session recreation, so it is safe to flip anytime (takes effect next turn). */
|
|
229
|
+
setParallel(id: string, parallel: boolean): Participant {
|
|
230
|
+
const p = this.participants.get(id)
|
|
231
|
+
if (!p) throw new Error(`unknown participant "${id}"`)
|
|
232
|
+
p.parallel = parallel
|
|
233
|
+
this.broadcastRoster()
|
|
234
|
+
this.onChange?.()
|
|
235
|
+
return p
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** In-place thinking level change — no session recreation. Takes effect next turn. */
|
|
239
|
+
async setThinkingLevel(
|
|
240
|
+
id: string,
|
|
241
|
+
level: "off" | "minimal" | "low" | "medium" | "high" | "xhigh",
|
|
242
|
+
): Promise<Participant> {
|
|
243
|
+
const p = this.participants.get(id)
|
|
244
|
+
if (!p) throw new Error(`unknown participant "${id}"`)
|
|
245
|
+
await p.setThinkingLevel(level)
|
|
246
|
+
this.broadcastRoster()
|
|
247
|
+
this.onChange?.()
|
|
248
|
+
return p
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
kick(id: string): void {
|
|
252
|
+
const p = this.participants.get(id)
|
|
253
|
+
if (!p) throw new Error(`unknown participant "${id}"`)
|
|
254
|
+
p.dispose()
|
|
255
|
+
this.participants.delete(id)
|
|
256
|
+
this.broadcastRoster()
|
|
257
|
+
this.onChange?.()
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Replace the whole roster with a saved persona set (fresh pi sessions).
|
|
261
|
+
* Used when loading/switching conversations. Does not fire onChange. */
|
|
262
|
+
async reset(states: PersonaState[]): Promise<void> {
|
|
263
|
+
const cb = this.onChange
|
|
264
|
+
this.onChange = null // suppress per-participant autosave during a bulk swap
|
|
265
|
+
try {
|
|
266
|
+
for (const p of this.participants.values()) p.dispose()
|
|
267
|
+
this.participants.clear()
|
|
268
|
+
for (const s of states) {
|
|
269
|
+
const { active, parallel, ...persona } = s
|
|
270
|
+
await this.create(persona, active, parallel ?? false)
|
|
271
|
+
}
|
|
272
|
+
} finally {
|
|
273
|
+
this.onChange = cb
|
|
274
|
+
}
|
|
275
|
+
this.broadcastRoster()
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
disposeAll(): void {
|
|
279
|
+
for (const p of this.participants.values()) p.dispose()
|
|
280
|
+
this.participants.clear()
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** True if a "provider/id" ref is a model the UI is allowed to assign. */
|
|
284
|
+
isAllowedModel(ref: string): boolean {
|
|
285
|
+
return isAllowedModel_(this.resolved, this.allowCloud, ref, this.explicitlyEnabledProviders)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ── Provider auth (for /provider slash command) ──────────────────────────
|
|
289
|
+
|
|
290
|
+
/** Get all providers with their auth status (no secrets). */
|
|
291
|
+
getProviderList(): Array<{ name: string; displayName: string; configured: boolean; models: number }> {
|
|
292
|
+
const all = this.resolved.modelRegistry.getAll()
|
|
293
|
+
const providerSet = new Set(all.map((m) => m.provider))
|
|
294
|
+
return Array.from(providerSet).map((name) => ({
|
|
295
|
+
name,
|
|
296
|
+
displayName: this.resolved.modelRegistry.getProviderDisplayName(name),
|
|
297
|
+
configured: this.resolved.modelRegistry.getProviderAuthStatus(name).configured,
|
|
298
|
+
models: all.filter((m) => m.provider === name).length,
|
|
299
|
+
}))
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Set an API key for a provider (persisted). Returns auth status, not the key. */
|
|
303
|
+
setProviderKey(name: string, key: string): { name: string; configured: boolean } {
|
|
304
|
+
this.resolved.authStorage.set(name, { type: "api_key", key })
|
|
305
|
+
this.explicitlyEnabledProviders.add(name)
|
|
306
|
+
this.resolved.modelRegistry.refresh()
|
|
307
|
+
return { name, configured: this.resolved.modelRegistry.getProviderAuthStatus(name).configured }
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Remove credentials for a provider. Returns auth status. */
|
|
311
|
+
removeProviderKey(name: string): { name: string; configured: boolean } {
|
|
312
|
+
this.resolved.authStorage.remove(name)
|
|
313
|
+
this.explicitlyEnabledProviders.delete(name)
|
|
314
|
+
this.resolved.modelRegistry.refresh()
|
|
315
|
+
return { name, configured: this.resolved.modelRegistry.getProviderAuthStatus(name).configured }
|
|
316
|
+
}
|
|
317
|
+
}
|