@evandu/super-pi 0.1.2 → 0.1.4
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/client/assets/index-BsQxHODf.js +43 -0
- package/dist/client/assets/{index-WlIFA_Dt.css → index-DP3T9VzH.css} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/agents.d.ts +20 -1
- package/dist/server/agents.js +92 -18
- package/dist/server/application/catalog-service.d.ts +22 -2
- package/dist/server/application/catalog-service.js +61 -9
- package/dist/server/application/conversation-service.d.ts +3 -2
- package/dist/server/application/conversation-service.js +5 -1
- package/dist/server/application/run-service.d.ts +3 -2
- package/dist/server/application/run-service.js +3 -0
- package/dist/server/application/workspace-service.d.ts +3 -2
- package/dist/server/application/workspace-service.js +5 -1
- package/dist/server/command-handler.js +8 -0
- package/dist/server/http-server.js +3 -1
- package/dist/server/index.js +27 -10
- package/dist/server/runtime.d.ts +5 -2
- package/dist/server/runtime.js +6 -2
- package/dist/server/server-context.d.ts +1 -0
- package/dist/server/store.d.ts +6 -0
- package/dist/server/store.js +16 -0
- package/dist/server/subagent-runs.d.ts +2 -0
- package/dist/server/subagent-runs.js +4 -0
- package/dist/server/subagents.d.ts +3 -2
- package/dist/server/subagents.js +5 -1
- package/dist/server/types.d.ts +19 -0
- package/dist/server/version.d.ts +10 -0
- package/dist/server/version.js +39 -0
- package/dist/server/ws-server.d.ts +2 -0
- package/dist/server/ws-server.js +12 -0
- package/package.json +4 -2
- package/dist/client/assets/index-CHw4ZoY4.js +0 -43
|
@@ -1,14 +1,34 @@
|
|
|
1
1
|
import type { AgentRuntime } from "../runtime.js";
|
|
2
2
|
import type { AgentCatalog, ModelCatalog, ModelSelection } from "../types.js";
|
|
3
3
|
import type { EventBroadcaster } from "../server-context.js";
|
|
4
|
+
import type { Store } from "../store.js";
|
|
4
5
|
/**
|
|
5
6
|
* Service managing model catalogs and agent definitions (primary and subagents).
|
|
6
7
|
*/
|
|
7
8
|
export declare class CatalogService {
|
|
8
9
|
private readonly runtime;
|
|
9
10
|
private readonly broadcaster;
|
|
10
|
-
private readonly
|
|
11
|
-
|
|
11
|
+
private readonly store?;
|
|
12
|
+
private readonly rootDir?;
|
|
13
|
+
private readonly onDirsChanged?;
|
|
14
|
+
private agentDirs;
|
|
15
|
+
constructor(runtime: AgentRuntime, broadcaster: EventBroadcaster, agentsDir: string | readonly string[], store?: Store | undefined, rootDir?: string | undefined, onDirsChanged?: ((dirs: string[]) => void) | undefined);
|
|
16
|
+
/**
|
|
17
|
+
* Returns current active agent discovery directories.
|
|
18
|
+
*/
|
|
19
|
+
getAgentDirs(): string[];
|
|
20
|
+
/**
|
|
21
|
+
* Updates agent discovery directories and notifies subscribers.
|
|
22
|
+
*/
|
|
23
|
+
setAgentDirs(dirs: string[]): void;
|
|
24
|
+
/**
|
|
25
|
+
* Updates user-configured custom agent directories in store and triggers rescan.
|
|
26
|
+
*/
|
|
27
|
+
setCustomDirs(dirs: string[]): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Immediately re-reads agent definitions from disk and broadcasts catalogs.
|
|
30
|
+
*/
|
|
31
|
+
rescan(): Promise<void>;
|
|
12
32
|
/**
|
|
13
33
|
* Fetches the model catalog from runtime, optionally tailored to a selected model.
|
|
14
34
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readAgents } from "../agents.js";
|
|
1
|
+
import { readAgents, resolveAgentDirectories } from "../agents.js";
|
|
2
2
|
function summarize(agent) {
|
|
3
3
|
const { prompt, toolFlags, ...summary } = agent;
|
|
4
4
|
const toolSummary = toolFlags.includes("--no-tools")
|
|
@@ -16,11 +16,54 @@ function summarize(agent) {
|
|
|
16
16
|
export class CatalogService {
|
|
17
17
|
runtime;
|
|
18
18
|
broadcaster;
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
store;
|
|
20
|
+
rootDir;
|
|
21
|
+
onDirsChanged;
|
|
22
|
+
agentDirs;
|
|
23
|
+
constructor(runtime, broadcaster, agentsDir, store, rootDir, onDirsChanged) {
|
|
21
24
|
this.runtime = runtime;
|
|
22
25
|
this.broadcaster = broadcaster;
|
|
23
|
-
this.
|
|
26
|
+
this.store = store;
|
|
27
|
+
this.rootDir = rootDir;
|
|
28
|
+
this.onDirsChanged = onDirsChanged;
|
|
29
|
+
this.agentDirs = Array.isArray(agentsDir) ? [...agentsDir] : [agentsDir];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Returns current active agent discovery directories.
|
|
33
|
+
*/
|
|
34
|
+
getAgentDirs() {
|
|
35
|
+
return [...this.agentDirs];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Updates agent discovery directories and notifies subscribers.
|
|
39
|
+
*/
|
|
40
|
+
setAgentDirs(dirs) {
|
|
41
|
+
this.agentDirs = [...dirs];
|
|
42
|
+
this.onDirsChanged?.(this.agentDirs);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Updates user-configured custom agent directories in store and triggers rescan.
|
|
46
|
+
*/
|
|
47
|
+
async setCustomDirs(dirs) {
|
|
48
|
+
if (this.store) {
|
|
49
|
+
await this.store.setCustomAgentDirs(dirs);
|
|
50
|
+
this.broadcaster.broadcastState();
|
|
51
|
+
}
|
|
52
|
+
const resolved = resolveAgentDirectories({
|
|
53
|
+
customDirs: dirs,
|
|
54
|
+
rootDir: this.rootDir,
|
|
55
|
+
});
|
|
56
|
+
this.setAgentDirs(resolved);
|
|
57
|
+
await this.rescan();
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Immediately re-reads agent definitions from disk and broadcasts catalogs.
|
|
61
|
+
*/
|
|
62
|
+
async rescan() {
|
|
63
|
+
await Promise.all([
|
|
64
|
+
this.broadcastAgents(),
|
|
65
|
+
this.broadcastSubagents(),
|
|
66
|
+
]);
|
|
24
67
|
}
|
|
25
68
|
/**
|
|
26
69
|
* Fetches the model catalog from runtime, optionally tailored to a selected model.
|
|
@@ -39,23 +82,32 @@ export class CatalogService {
|
|
|
39
82
|
* Reads one catalog filtered by mode at source.
|
|
40
83
|
*/
|
|
41
84
|
async readAgentCatalog(mode) {
|
|
85
|
+
const primaryDir = this.agentDirs[0] ?? "";
|
|
42
86
|
try {
|
|
43
|
-
const scan = await readAgents(this.
|
|
87
|
+
const scan = await readAgents(this.agentDirs, { mode });
|
|
44
88
|
for (const problem of scan.problems) {
|
|
45
|
-
console.warn(`Super Pi: ${
|
|
89
|
+
console.warn(`Super Pi: ${problem.file} is not a usable agent - ${problem.message}`);
|
|
46
90
|
}
|
|
47
91
|
const agents = scan.agents.filter((agent) => !agent.hidden).map(summarize);
|
|
48
92
|
if (!agents.length) {
|
|
49
|
-
console.warn(`Super Pi: no ${mode} agents found in ${this.
|
|
93
|
+
console.warn(`Super Pi: no ${mode} agents found in [${this.agentDirs.join(", ")}] (${scan.problems.length} file(s) rejected).`);
|
|
50
94
|
}
|
|
51
|
-
return {
|
|
95
|
+
return {
|
|
96
|
+
source: "directory",
|
|
97
|
+
agents,
|
|
98
|
+
problems: scan.problems,
|
|
99
|
+
directory: primaryDir,
|
|
100
|
+
directories: [...this.agentDirs],
|
|
101
|
+
fetchedAt: new Date().toISOString(),
|
|
102
|
+
};
|
|
52
103
|
}
|
|
53
104
|
catch (error) {
|
|
54
105
|
return {
|
|
55
106
|
source: "unavailable",
|
|
56
107
|
agents: [],
|
|
57
108
|
problems: [],
|
|
58
|
-
directory:
|
|
109
|
+
directory: primaryDir,
|
|
110
|
+
directories: [...this.agentDirs],
|
|
59
111
|
fetchedAt: new Date().toISOString(),
|
|
60
112
|
error: error instanceof Error ? error.message : "unknown error",
|
|
61
113
|
};
|
|
@@ -27,8 +27,9 @@ export declare class ConversationService {
|
|
|
27
27
|
private readonly catalogService;
|
|
28
28
|
private readonly runService;
|
|
29
29
|
private readonly extensionUiService;
|
|
30
|
-
private
|
|
31
|
-
constructor(store: Store, runtime: AgentRuntime, subagents: SubagentRunRecorder, broadcaster: EventBroadcaster, catalogService: CatalogService, runService: RunService, extensionUiService: ExtensionUiService, agentsDir: string);
|
|
30
|
+
private agentsDir;
|
|
31
|
+
constructor(store: Store, runtime: AgentRuntime, subagents: SubagentRunRecorder, broadcaster: EventBroadcaster, catalogService: CatalogService, runService: RunService, extensionUiService: ExtensionUiService, agentsDir: string | string[]);
|
|
32
|
+
setAgentsDir(dir: string | string[]): void;
|
|
32
33
|
/**
|
|
33
34
|
* Creates a new conversation in a workspace.
|
|
34
35
|
*/
|
|
@@ -22,6 +22,9 @@ export class ConversationService {
|
|
|
22
22
|
this.extensionUiService = extensionUiService;
|
|
23
23
|
this.agentsDir = agentsDir;
|
|
24
24
|
}
|
|
25
|
+
setAgentsDir(dir) {
|
|
26
|
+
this.agentsDir = dir;
|
|
27
|
+
}
|
|
25
28
|
/**
|
|
26
29
|
* Creates a new conversation in a workspace.
|
|
27
30
|
*/
|
|
@@ -101,7 +104,8 @@ export class ConversationService {
|
|
|
101
104
|
}
|
|
102
105
|
const agent = await this.runtime.agentFor?.({ id: conversation.id, agentId });
|
|
103
106
|
if (!agent) {
|
|
104
|
-
|
|
107
|
+
const dirsLabel = Array.isArray(this.agentsDir) ? this.agentsDir.join(", ") : this.agentsDir;
|
|
108
|
+
throw new Error(`Agent ${JSON.stringify(agentId)} 不存在或不是可用的 primary agent(目录:${dirsLabel})`);
|
|
105
109
|
}
|
|
106
110
|
if (agent.model) {
|
|
107
111
|
const catalog = await this.runtime.catalog();
|
|
@@ -43,10 +43,11 @@ export declare class RunService {
|
|
|
43
43
|
private readonly subagents;
|
|
44
44
|
private readonly broadcaster;
|
|
45
45
|
private readonly extensionUiService;
|
|
46
|
-
private
|
|
46
|
+
private agentsDir;
|
|
47
47
|
private readonly workflows;
|
|
48
48
|
private readonly activeRuns;
|
|
49
|
-
constructor(store: Store, runtime: AgentRuntime, subagents: SubagentRunRecorder, broadcaster: EventBroadcaster, extensionUiService: ExtensionUiService, agentsDir: string, workflows: WorkflowEngine);
|
|
49
|
+
constructor(store: Store, runtime: AgentRuntime, subagents: SubagentRunRecorder, broadcaster: EventBroadcaster, extensionUiService: ExtensionUiService, agentsDir: string | string[], workflows: WorkflowEngine);
|
|
50
|
+
setAgentsDir(dir: string | string[]): void;
|
|
50
51
|
/**
|
|
51
52
|
* Resolves the conversation and workspace synchronously for a run.
|
|
52
53
|
*/
|
|
@@ -23,8 +23,9 @@ export declare class WorkspaceService {
|
|
|
23
23
|
private readonly runService;
|
|
24
24
|
private readonly extensionUiService;
|
|
25
25
|
private readonly uploadsDir;
|
|
26
|
-
private
|
|
27
|
-
constructor(store: Store, runtime: AgentRuntime, broadcaster: EventBroadcaster, catalogService: CatalogService, runService: RunService, extensionUiService: ExtensionUiService, uploadsDir: string, agentsDir: string);
|
|
26
|
+
private agentsDir;
|
|
27
|
+
constructor(store: Store, runtime: AgentRuntime, broadcaster: EventBroadcaster, catalogService: CatalogService, runService: RunService, extensionUiService: ExtensionUiService, uploadsDir: string, agentsDir: string | string[]);
|
|
28
|
+
setAgentsDir(dir: string | string[]): void;
|
|
28
29
|
/**
|
|
29
30
|
* Creates a new workspace.
|
|
30
31
|
*/
|
|
@@ -21,6 +21,9 @@ export class WorkspaceService {
|
|
|
21
21
|
this.uploadsDir = uploadsDir;
|
|
22
22
|
this.agentsDir = agentsDir;
|
|
23
23
|
}
|
|
24
|
+
setAgentsDir(dir) {
|
|
25
|
+
this.agentsDir = dir;
|
|
26
|
+
}
|
|
24
27
|
/**
|
|
25
28
|
* Creates a new workspace.
|
|
26
29
|
*/
|
|
@@ -91,7 +94,8 @@ export class WorkspaceService {
|
|
|
91
94
|
}
|
|
92
95
|
const agent = await this.runtime.agentFor({ id: workspaceId, agentId: settings.agentId });
|
|
93
96
|
if (!agent) {
|
|
94
|
-
|
|
97
|
+
const dirsLabel = Array.isArray(this.agentsDir) ? this.agentsDir.join(", ") : this.agentsDir;
|
|
98
|
+
throw new Error(`Agent ${JSON.stringify(settings.agentId)} 不存在或不是可用的 primary agent(目录:${dirsLabel})`);
|
|
95
99
|
}
|
|
96
100
|
agentToStore = agent.id;
|
|
97
101
|
}
|
|
@@ -136,6 +136,14 @@ export class CommandHandler {
|
|
|
136
136
|
await this.catalogService.broadcastSubagents();
|
|
137
137
|
return;
|
|
138
138
|
}
|
|
139
|
+
if (command.type === "agents.setCustomDirs") {
|
|
140
|
+
await this.catalogService.setCustomDirs(command.dirs);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (command.type === "agents.rescan") {
|
|
144
|
+
await this.catalogService.rescan();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
139
147
|
throw new Error(`未知的命令类型:${JSON.stringify(command.type ?? null)}`);
|
|
140
148
|
}
|
|
141
149
|
}
|
|
@@ -4,6 +4,7 @@ import { createServer } from "node:http";
|
|
|
4
4
|
import { basename, dirname, extname, join, relative, resolve } from "node:path";
|
|
5
5
|
import { describe as describeFilesError, fileMeta, listDirectory, MAX_FILE_BYTES, readWorkspaceFile, resolveInsideRoot, searchByName, toRelative, writeWorkspaceFile, } from "./files.js";
|
|
6
6
|
import { findUploadFile, MAX_UPLOAD_BODY_BYTES, saveUpload } from "./upload.js";
|
|
7
|
+
import { checkDiskVersion } from "./version.js";
|
|
7
8
|
const MIME_TYPES = {
|
|
8
9
|
".css": "text/css; charset=utf-8",
|
|
9
10
|
".html": "text/html; charset=utf-8",
|
|
@@ -51,8 +52,9 @@ export function createHttpServer(options) {
|
|
|
51
52
|
return;
|
|
52
53
|
}
|
|
53
54
|
if (url.pathname === "/api/health") {
|
|
55
|
+
const version = checkDiskVersion(rootDir);
|
|
54
56
|
response.writeHead(200, { "content-type": "application/json; charset=utf-8" })
|
|
55
|
-
.end(JSON.stringify({ status: "ok", service: "super-pi" }));
|
|
57
|
+
.end(JSON.stringify({ status: "ok", service: "super-pi", ...version }));
|
|
56
58
|
return;
|
|
57
59
|
}
|
|
58
60
|
if (url.pathname === "/api/subagent/dispatch") {
|
package/dist/server/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { homedir } from "node:os";
|
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
5
|
import { defaultStorePath, Store } from "./store.js";
|
|
6
6
|
import { PiRpcRuntime, WelcomeRuntime } from "./runtime.js";
|
|
7
|
+
import { resolveAgentDirectories } from "./agents.js";
|
|
7
8
|
import { releaseAllCapabilities } from "./capability.js";
|
|
8
9
|
import { SubagentSupervisor } from "./subagents.js";
|
|
9
10
|
import { SubagentRunRecorder } from "./subagent-runs.js";
|
|
@@ -16,15 +17,16 @@ import { WorkspaceService } from "./application/workspace-service.js";
|
|
|
16
17
|
import { CommandHandler } from "./command-handler.js";
|
|
17
18
|
import { createHttpServer } from "./http-server.js";
|
|
18
19
|
import { WsBroadcaster, WsServer } from "./ws-server.js";
|
|
20
|
+
import { initRunningVersion } from "./version.js";
|
|
19
21
|
// ------------------------------------------------------------- Environment & paths
|
|
20
22
|
const rootDir = resolve(import.meta.dirname, "../..");
|
|
23
|
+
initRunningVersion(rootDir);
|
|
21
24
|
const port = Number.parseInt(process.env.SUPER_PI_PORT ?? "3080", 10);
|
|
22
25
|
const host = process.env.SUPER_PI_HOST ?? "127.0.0.1";
|
|
23
26
|
const defaultDataDir = !rootDir.includes("node_modules") && existsSync(join(rootDir, ".super-pi"))
|
|
24
27
|
? join(rootDir, ".super-pi")
|
|
25
28
|
: join(homedir(), ".super-pi");
|
|
26
29
|
const dataDir = resolve(process.env.SUPER_PI_DATA_DIR ?? defaultDataDir);
|
|
27
|
-
const agentsDir = resolve(process.env.SUPER_PI_AGENTS_DIR ?? join(rootDir, "agents"));
|
|
28
30
|
const agentPromptDir = join(dataDir, "agent-prompts");
|
|
29
31
|
const clientDir = join(rootDir, "dist", "client");
|
|
30
32
|
const accessToken = process.env.SUPER_PI_ACCESS_TOKEN;
|
|
@@ -33,23 +35,29 @@ await mkdir(uploadsDir, { recursive: true }).catch(() => { });
|
|
|
33
35
|
const taskEndpoint = `http://${host === "0.0.0.0" ? "127.0.0.1" : host}:${port}/api/subagent/dispatch`;
|
|
34
36
|
const todoEndpoint = `http://${host === "0.0.0.0" ? "127.0.0.1" : host}:${port}/api/todos`;
|
|
35
37
|
const workflowEndpoint = `http://${host === "0.0.0.0" ? "127.0.0.1" : host}:${port}/api/workflow/run`;
|
|
38
|
+
// ------------------------------------------------------------- Core singletons
|
|
39
|
+
const store = await Store.open(defaultStorePath(dataDir));
|
|
40
|
+
const initialAgentDirs = resolveAgentDirectories({
|
|
41
|
+
customDirs: store.snapshot().customAgentDirs,
|
|
42
|
+
rootDir,
|
|
43
|
+
});
|
|
44
|
+
const agentsDir = initialAgentDirs[0] ?? resolve(process.env.SUPER_PI_AGENTS_DIR ?? join(rootDir, "agents"));
|
|
36
45
|
const paths = {
|
|
37
46
|
dataDir,
|
|
38
47
|
rootDir,
|
|
39
48
|
agentsDir,
|
|
49
|
+
agentDirs: initialAgentDirs,
|
|
40
50
|
agentPromptDir,
|
|
41
51
|
uploadsDir,
|
|
42
52
|
clientDir,
|
|
43
53
|
};
|
|
44
|
-
// ------------------------------------------------------------- Core singletons
|
|
45
|
-
const store = await Store.open(defaultStorePath(dataDir));
|
|
46
54
|
const runtime = process.env.SUPER_PI_RUNTIME === "welcome"
|
|
47
55
|
? new WelcomeRuntime()
|
|
48
|
-
: new PiRpcRuntime(join(dataDir, "pi-sessions"), undefined, { dir:
|
|
56
|
+
: new PiRpcRuntime(join(dataDir, "pi-sessions"), undefined, { dir: initialAgentDirs, promptDir: agentPromptDir }, { endpoint: taskEndpoint, todoEndpoint, workflowEndpoint, uploadsDir });
|
|
49
57
|
const broadcaster = new WsBroadcaster(() => store.snapshot());
|
|
50
58
|
const extensionUiService = new ExtensionUiService(broadcaster);
|
|
51
59
|
const supervisor = new SubagentSupervisor(join(dataDir, "pi-sessions"), undefined, {
|
|
52
|
-
dir:
|
|
60
|
+
dir: initialAgentDirs,
|
|
53
61
|
promptDir: agentPromptDir,
|
|
54
62
|
});
|
|
55
63
|
const subagents = new SubagentRunRecorder(supervisor, {
|
|
@@ -80,10 +88,19 @@ const workflows = new WorkflowEngine(subagents, {
|
|
|
80
88
|
},
|
|
81
89
|
});
|
|
82
90
|
// ------------------------------------------------------------- Application services
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
const onDirsChanged = (dirs) => {
|
|
92
|
+
paths.agentDirs = dirs;
|
|
93
|
+
paths.agentsDir = dirs[0] ?? "";
|
|
94
|
+
runtime.setAgentsDir?.(dirs);
|
|
95
|
+
subagents.setAgentsDir(dirs);
|
|
96
|
+
runService.setAgentsDir(dirs);
|
|
97
|
+
conversationService.setAgentsDir(dirs);
|
|
98
|
+
workspaceService.setAgentsDir(dirs);
|
|
99
|
+
};
|
|
100
|
+
const catalogService = new CatalogService(runtime, broadcaster, initialAgentDirs, store, rootDir, onDirsChanged);
|
|
101
|
+
const runService = new RunService(store, runtime, subagents, broadcaster, extensionUiService, initialAgentDirs, workflows);
|
|
102
|
+
const conversationService = new ConversationService(store, runtime, subagents, broadcaster, catalogService, runService, extensionUiService, initialAgentDirs);
|
|
103
|
+
const workspaceService = new WorkspaceService(store, runtime, broadcaster, catalogService, runService, extensionUiService, uploadsDir, initialAgentDirs);
|
|
87
104
|
const serverContext = {
|
|
88
105
|
paths,
|
|
89
106
|
store,
|
|
@@ -120,7 +137,7 @@ process.on("SIGINT", () => shutdown("SIGINT"));
|
|
|
120
137
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
121
138
|
// ------------------------------------------------------------- Transports & startup
|
|
122
139
|
const httpServer = createHttpServer({ context: serverContext, onShutdown: shutdown });
|
|
123
|
-
const wsServer = new WsServer({ httpServer, broadcaster, commandHandler, catalogService, accessToken });
|
|
140
|
+
const wsServer = new WsServer({ httpServer, broadcaster, commandHandler, catalogService, accessToken, rootDir });
|
|
124
141
|
httpServer.listen(port, host, () => {
|
|
125
142
|
console.log(`Super Pi is running at http://${host}:${port}`);
|
|
126
143
|
});
|
package/dist/server/runtime.d.ts
CHANGED
|
@@ -199,6 +199,8 @@ export interface AgentRuntime {
|
|
|
199
199
|
* refusal, exactly as `revert`'s absence is, rather than passed over.
|
|
200
200
|
*/
|
|
201
201
|
fork?(request: ForkRequest): Promise<ForkOutcome>;
|
|
202
|
+
/** Updates the active agent directory or directories to search. */
|
|
203
|
+
setAgentsDir?(dir: string | string[]): void;
|
|
202
204
|
}
|
|
203
205
|
/** Deterministic offline runtime used by tests and local UI development. */
|
|
204
206
|
export declare class WelcomeRuntime implements AgentRuntime {
|
|
@@ -527,7 +529,7 @@ export declare class PiRpcRuntime implements AgentRuntime {
|
|
|
527
529
|
* call site keeps its meaning - moving the agent directory into that slot would have silently fed a
|
|
528
530
|
* launcher's file path in as a directory of agent definitions.
|
|
529
531
|
*/
|
|
530
|
-
private
|
|
532
|
+
private agents;
|
|
531
533
|
/**
|
|
532
534
|
* How the `task` extension reaches this server. A FOURTH positional parameter for the same reason as
|
|
533
535
|
* the third: an options bag in one of the earlier slots would silently change what an existing
|
|
@@ -587,7 +589,7 @@ export declare class PiRpcRuntime implements AgentRuntime {
|
|
|
587
589
|
* launcher's file path in as a directory of agent definitions.
|
|
588
590
|
*/
|
|
589
591
|
agents?: {
|
|
590
|
-
dir: string;
|
|
592
|
+
dir: string | string[];
|
|
591
593
|
promptDir: string;
|
|
592
594
|
},
|
|
593
595
|
/**
|
|
@@ -624,6 +626,7 @@ export declare class PiRpcRuntime implements AgentRuntime {
|
|
|
624
626
|
* as the conversation's own role.
|
|
625
627
|
*/
|
|
626
628
|
agentFor(conversation: Pick<Conversation, "id" | "agentId">): Promise<AgentDefinition | undefined>;
|
|
629
|
+
setAgentsDir(dir: string | string[]): void;
|
|
627
630
|
/**
|
|
628
631
|
* The `--extension` argv for this turn, or `[]` when the task tool is not available.
|
|
629
632
|
*
|
package/dist/server/runtime.js
CHANGED
|
@@ -631,9 +631,13 @@ export class PiRpcRuntime {
|
|
|
631
631
|
const agent = agents.find((item) => item.id === conversation.agentId);
|
|
632
632
|
if (agent)
|
|
633
633
|
return agent;
|
|
634
|
-
|
|
634
|
+
const dirLabel = Array.isArray(this.agents.dir) ? this.agents.dir.join(", ") : this.agents.dir;
|
|
635
|
+
console.warn(`PiRpcRuntime: conversation ${conversation.id} names agent ${JSON.stringify(conversation.agentId)}, which is not a primary agent in ${dirLabel}${problems.length ? ` (${problems.length} file(s) there are not usable agents)` : ""} - running as Pi itself.`);
|
|
635
636
|
return undefined;
|
|
636
637
|
}
|
|
638
|
+
setAgentsDir(dir) {
|
|
639
|
+
this.agents.dir = dir;
|
|
640
|
+
}
|
|
637
641
|
/**
|
|
638
642
|
* The `--extension` argv for this turn, or `[]` when the task tool is not available.
|
|
639
643
|
*
|
|
@@ -677,7 +681,7 @@ export class PiRpcRuntime {
|
|
|
677
681
|
async taskExtensionEnv(conversationId) {
|
|
678
682
|
const { agents, problems } = await readAgents(this.agents.dir, { mode: "subagent" });
|
|
679
683
|
for (const problem of problems)
|
|
680
|
-
console.warn(`PiRpcRuntime: ${
|
|
684
|
+
console.warn(`PiRpcRuntime: ${problem.file} is not a usable sub-agent - ${problem.message}`);
|
|
681
685
|
return {
|
|
682
686
|
[TASK_ENDPOINT_ENV]: this.options.endpoint ?? "",
|
|
683
687
|
[TASK_ROSTER_ENV]: rosterPayload(agents),
|
package/dist/server/store.d.ts
CHANGED
|
@@ -448,6 +448,12 @@ export declare class Store {
|
|
|
448
448
|
todos: TodoItem[];
|
|
449
449
|
dropped: number;
|
|
450
450
|
}>;
|
|
451
|
+
/**
|
|
452
|
+
* Updates user-configured custom agent discovery directories.
|
|
453
|
+
* Empty/whitespace paths are ignored, and if no valid paths remain, the key is deleted
|
|
454
|
+
* to keep state.json clean.
|
|
455
|
+
*/
|
|
456
|
+
setCustomAgentDirs(dirs: string[]): Promise<string[]>;
|
|
451
457
|
/**
|
|
452
458
|
* Writes the whole state atomically: a temp file, then a rename over the real one.
|
|
453
459
|
*
|
package/dist/server/store.js
CHANGED
|
@@ -1194,6 +1194,22 @@ export class Store {
|
|
|
1194
1194
|
await this.persist();
|
|
1195
1195
|
return { todos: structuredClone(todos), dropped };
|
|
1196
1196
|
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Updates user-configured custom agent discovery directories.
|
|
1199
|
+
* Empty/whitespace paths are ignored, and if no valid paths remain, the key is deleted
|
|
1200
|
+
* to keep state.json clean.
|
|
1201
|
+
*/
|
|
1202
|
+
async setCustomAgentDirs(dirs) {
|
|
1203
|
+
const cleaned = Array.from(new Set(dirs.map((d) => d.trim()).filter(Boolean)));
|
|
1204
|
+
if (cleaned.length) {
|
|
1205
|
+
this.state.customAgentDirs = cleaned;
|
|
1206
|
+
}
|
|
1207
|
+
else {
|
|
1208
|
+
delete this.state.customAgentDirs;
|
|
1209
|
+
}
|
|
1210
|
+
await this.persist();
|
|
1211
|
+
return structuredClone(cleaned);
|
|
1212
|
+
}
|
|
1197
1213
|
/**
|
|
1198
1214
|
* Writes the whole state atomically: a temp file, then a rename over the real one.
|
|
1199
1215
|
*
|
|
@@ -89,6 +89,8 @@ export declare class SubagentRunRecorder {
|
|
|
89
89
|
*/
|
|
90
90
|
private readonly controllers;
|
|
91
91
|
constructor(supervisor: SubagentSupervisor, hooks: SubagentRunHooks);
|
|
92
|
+
/** Updates agent discovery directories on the underlying supervisor. */
|
|
93
|
+
setAgentsDir(dir: string | string[]): void;
|
|
92
94
|
/** Every child still running, optionally narrowed to one conversation. The supervisor owns the pids. */
|
|
93
95
|
liveRuns(conversationId?: string): {
|
|
94
96
|
id: string;
|
|
@@ -44,6 +44,10 @@ export class SubagentRunRecorder {
|
|
|
44
44
|
this.supervisor = supervisor;
|
|
45
45
|
this.hooks = hooks;
|
|
46
46
|
}
|
|
47
|
+
/** Updates agent discovery directories on the underlying supervisor. */
|
|
48
|
+
setAgentsDir(dir) {
|
|
49
|
+
this.supervisor.setAgentsDir?.(dir);
|
|
50
|
+
}
|
|
47
51
|
/** Every child still running, optionally narrowed to one conversation. The supervisor owns the pids. */
|
|
48
52
|
liveRuns(conversationId) {
|
|
49
53
|
return this.supervisor.liveRuns(conversationId);
|
|
@@ -136,7 +136,7 @@ export declare class SubagentCancelled extends Error {
|
|
|
136
136
|
/** Spawns sub-agent children Pi and drains their JSONL RPC streams. */
|
|
137
137
|
export declare class SubagentSupervisor {
|
|
138
138
|
private readonly sessionDir;
|
|
139
|
-
private
|
|
139
|
+
private agents;
|
|
140
140
|
private readonly pi;
|
|
141
141
|
/** Live children by run id, so a caller can name the process it means. Cleared in the run's `finally`. */
|
|
142
142
|
private readonly children;
|
|
@@ -147,7 +147,7 @@ export declare class SubagentSupervisor {
|
|
|
147
147
|
* child's spawn line depend on when it was started.
|
|
148
148
|
*/
|
|
149
149
|
constructor(sessionDir: string, command?: string, agents?: {
|
|
150
|
-
dir: string;
|
|
150
|
+
dir: string | string[];
|
|
151
151
|
promptDir: string;
|
|
152
152
|
});
|
|
153
153
|
/**
|
|
@@ -163,6 +163,7 @@ export declare class SubagentSupervisor {
|
|
|
163
163
|
conversationId: string;
|
|
164
164
|
pid: number | undefined;
|
|
165
165
|
}[];
|
|
166
|
+
setAgentsDir(dir: string | string[]): void;
|
|
166
167
|
/**
|
|
167
168
|
* Runs one sub-agent to completion and returns what it said.
|
|
168
169
|
*
|
package/dist/server/subagents.js
CHANGED
|
@@ -195,6 +195,9 @@ export class SubagentSupervisor {
|
|
|
195
195
|
liveRuns(conversationId) {
|
|
196
196
|
return [...this.children].map(([id, run]) => ({ id, conversationId: run.conversationId, pid: run.child.pid }));
|
|
197
197
|
}
|
|
198
|
+
setAgentsDir(dir) {
|
|
199
|
+
this.agents.dir = dir;
|
|
200
|
+
}
|
|
198
201
|
/**
|
|
199
202
|
* Runs one sub-agent to completion and returns what it said.
|
|
200
203
|
*
|
|
@@ -537,6 +540,7 @@ export class SubagentSupervisor {
|
|
|
537
540
|
const agent = agents.find((item) => item.id === agentId);
|
|
538
541
|
if (agent)
|
|
539
542
|
return agent;
|
|
540
|
-
|
|
543
|
+
const dirLabel = Array.isArray(this.agents.dir) ? this.agents.dir.join(", ") : this.agents.dir;
|
|
544
|
+
throw new Error(`sub-agent ${JSON.stringify(agentId)} is not a subagent definition in ${dirLabel}${problems.length ? ` (${problems.length} file(s) there are not usable agents)` : ""}. A sub-agent is an agents/*.md file with \`mode: subagent\`; a primary agent is chosen by a human and is deliberately not dispatchable as one.`);
|
|
541
545
|
}
|
|
542
546
|
}
|
package/dist/server/types.d.ts
CHANGED
|
@@ -254,6 +254,8 @@ export type AgentDefinition = {
|
|
|
254
254
|
*/
|
|
255
255
|
tools?: Record<string, unknown>;
|
|
256
256
|
prompt: string;
|
|
257
|
+
/** The directory this agent definition was discovered from. */
|
|
258
|
+
sourceDir?: string;
|
|
257
259
|
};
|
|
258
260
|
/**
|
|
259
261
|
* What the picker receives. The prompt body is deliberately NOT sent: it is a whole system prompt,
|
|
@@ -261,6 +263,7 @@ export type AgentDefinition = {
|
|
|
261
263
|
*/
|
|
262
264
|
export type AgentSummary = Omit<AgentDefinition, "prompt" | "toolFlags" | "tools"> & {
|
|
263
265
|
toolSummary: string;
|
|
266
|
+
sourceDir?: string;
|
|
264
267
|
};
|
|
265
268
|
export type AgentCatalog = {
|
|
266
269
|
source: "directory" | "unavailable";
|
|
@@ -271,6 +274,8 @@ export type AgentCatalog = {
|
|
|
271
274
|
message: string;
|
|
272
275
|
}[];
|
|
273
276
|
directory: string;
|
|
277
|
+
/** All searched agent directories in priority order. */
|
|
278
|
+
directories?: string[];
|
|
274
279
|
fetchedAt: string;
|
|
275
280
|
error?: string;
|
|
276
281
|
};
|
|
@@ -496,6 +501,7 @@ export type AppState = {
|
|
|
496
501
|
backgroundTasks: BackgroundTaskSummary[];
|
|
497
502
|
todos?: TodoItem[];
|
|
498
503
|
workflows?: WorkflowRun[];
|
|
504
|
+
customAgentDirs?: string[];
|
|
499
505
|
};
|
|
500
506
|
export type TodoItem = {
|
|
501
507
|
id?: string;
|
|
@@ -811,12 +817,25 @@ export type ClientCommand = {
|
|
|
811
817
|
/** Asks for the `mode: subagent` half of the agent directory. Answered by `subagent.catalog`. */
|
|
812
818
|
| {
|
|
813
819
|
type: "subagent.catalog.fetch";
|
|
820
|
+
} | {
|
|
821
|
+
type: "agents.setCustomDirs";
|
|
822
|
+
dirs: string[];
|
|
823
|
+
} | {
|
|
824
|
+
type: "agents.rescan";
|
|
814
825
|
} | ({
|
|
815
826
|
type: "extension.ui.answer";
|
|
816
827
|
conversationId: string;
|
|
817
828
|
requestId: string;
|
|
818
829
|
} & ExtensionUiAnswer);
|
|
830
|
+
export type ServerVersionInfo = {
|
|
831
|
+
runningVersion: string;
|
|
832
|
+
diskVersion: string;
|
|
833
|
+
needsRestart: boolean;
|
|
834
|
+
};
|
|
819
835
|
export type ServerEvent = {
|
|
836
|
+
type: "server.version";
|
|
837
|
+
version: ServerVersionInfo;
|
|
838
|
+
} | {
|
|
820
839
|
type: "state";
|
|
821
840
|
state: AppState;
|
|
822
841
|
} | {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ServerVersionInfo } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Initializes the version captured when this server process booted up.
|
|
4
|
+
*/
|
|
5
|
+
export declare function initRunningVersion(rootDir: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Checks whether the package.json version on disk has been updated beyond
|
|
8
|
+
* the version currently executing in this process.
|
|
9
|
+
*/
|
|
10
|
+
export declare function checkDiskVersion(rootDir: string): ServerVersionInfo;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
let runningVersion = "0.1.3";
|
|
4
|
+
/**
|
|
5
|
+
* Initializes the version captured when this server process booted up.
|
|
6
|
+
*/
|
|
7
|
+
export function initRunningVersion(rootDir) {
|
|
8
|
+
try {
|
|
9
|
+
const pkg = JSON.parse(readFileSync(join(rootDir, "package.json"), "utf8"));
|
|
10
|
+
if (pkg.version)
|
|
11
|
+
runningVersion = pkg.version;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
// fallback to default
|
|
15
|
+
}
|
|
16
|
+
return runningVersion;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Checks whether the package.json version on disk has been updated beyond
|
|
20
|
+
* the version currently executing in this process.
|
|
21
|
+
*/
|
|
22
|
+
export function checkDiskVersion(rootDir) {
|
|
23
|
+
try {
|
|
24
|
+
const pkg = JSON.parse(readFileSync(join(rootDir, "package.json"), "utf8"));
|
|
25
|
+
const diskVersion = pkg.version ?? runningVersion;
|
|
26
|
+
return {
|
|
27
|
+
runningVersion,
|
|
28
|
+
diskVersion,
|
|
29
|
+
needsRestart: diskVersion !== runningVersion,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return {
|
|
34
|
+
runningVersion,
|
|
35
|
+
diskVersion: runningVersion,
|
|
36
|
+
needsRestart: false,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|