@evandu/super-pi 0.1.1 → 0.1.3
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/agents/super-pi-task/core.ts +748 -485
- package/agents/super-pi-task/index.ts +198 -141
- package/dist/client/assets/{index-CsbZzuMy.css → index-BhFeQ1E-.css} +1 -1
- package/dist/client/assets/index-D_VwFuME.js +43 -0
- 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 +49 -1
- package/dist/server/application/run-service.js +81 -2
- package/dist/server/application/workspace-service.d.ts +3 -2
- package/dist/server/application/workspace-service.js +5 -1
- package/dist/server/capability.d.ts +89 -0
- package/dist/server/capability.js +136 -0
- package/dist/server/command-handler.js +12 -0
- package/dist/server/http-server.js +20 -0
- package/dist/server/index.js +47 -9
- package/dist/server/runtime.d.ts +6 -2
- package/dist/server/runtime.js +19 -4
- package/dist/server/server-context.d.ts +1 -0
- package/dist/server/store.d.ts +48 -0
- package/dist/server/store.js +160 -1
- 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/task-tool.d.ts +21 -0
- package/dist/server/task-tool.js +21 -0
- package/dist/server/types.d.ts +45 -0
- package/dist/server/workflow-engine.d.ts +129 -0
- package/dist/server/workflow-engine.js +253 -0
- package/dist/server/workflow.d.ts +287 -0
- package/dist/server/workflow.js +387 -0
- package/package.json +8 -2
- package/dist/client/assets/index-BMQsEdio.js +0 -24
package/dist/client/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta name="theme-color" content="#101828" />
|
|
7
7
|
<title>Super Pi</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-D_VwFuME.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BhFeQ1E-.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
package/dist/server/agents.d.ts
CHANGED
|
@@ -146,7 +146,26 @@ export type AgentModeFilter = "primary" | "subagent";
|
|
|
146
146
|
* so the split is a filter on the parsed field, never a second directory or a second parser. A caller
|
|
147
147
|
* with no argument gets exactly what it always got.
|
|
148
148
|
*/
|
|
149
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Expands a leading tilde (`~` or `~/...` or `~\...`) to the user's home directory.
|
|
151
|
+
*/
|
|
152
|
+
export declare function expandTilde(filepath: string): string;
|
|
153
|
+
export interface ResolveAgentDirectoriesOptions {
|
|
154
|
+
customDirs?: readonly string[];
|
|
155
|
+
env?: NodeJS.ProcessEnv;
|
|
156
|
+
rootDir?: string;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Resolves the ordered list of directories to discover agents from:
|
|
160
|
+
* 1. Custom directories configured by the user in app settings (or options)
|
|
161
|
+
* 2. SUPER_PI_AGENTS_DIR environment variable if set
|
|
162
|
+
* 3. User global agent directories (~/.super_pi/agents, ~/.super-pi/agents, ~/.pi/agents)
|
|
163
|
+
* 4. Built-in repository/package agents directory (<rootDir>/agents)
|
|
164
|
+
*
|
|
165
|
+
* Duplicate paths are preserved in order of first appearance.
|
|
166
|
+
*/
|
|
167
|
+
export declare function resolveAgentDirectories(options?: ResolveAgentDirectoriesOptions): string[];
|
|
168
|
+
export declare function readAgents(directory: string | readonly string[], options?: {
|
|
150
169
|
mode?: AgentModeFilter;
|
|
151
170
|
}): Promise<AgentScan>;
|
|
152
171
|
/** Parses one definition file, or returns the reason it is not a usable agent. */
|
package/dist/server/agents.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import {
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
3
4
|
/**
|
|
4
5
|
* A guard rather than a cast: it is the only thing that lets `tools:` be checked once and then used as
|
|
5
6
|
* a map everywhere without the compiler complaining. It narrows to the index-signature shape that
|
|
@@ -332,32 +333,105 @@ export function parseAgentModel(model) {
|
|
|
332
333
|
* so the split is a filter on the parsed field, never a second directory or a second parser. A caller
|
|
333
334
|
* with no argument gets exactly what it always got.
|
|
334
335
|
*/
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
336
|
+
/**
|
|
337
|
+
* Expands a leading tilde (`~` or `~/...` or `~\...`) to the user's home directory.
|
|
338
|
+
*/
|
|
339
|
+
export function expandTilde(filepath) {
|
|
340
|
+
if (filepath === "~")
|
|
341
|
+
return homedir();
|
|
342
|
+
if (filepath.startsWith("~/") || filepath.startsWith("~\\")) {
|
|
343
|
+
return join(homedir(), filepath.slice(2));
|
|
340
344
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
+
return filepath;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Resolves the ordered list of directories to discover agents from:
|
|
349
|
+
* 1. Custom directories configured by the user in app settings (or options)
|
|
350
|
+
* 2. SUPER_PI_AGENTS_DIR environment variable if set
|
|
351
|
+
* 3. User global agent directories (~/.super_pi/agents, ~/.super-pi/agents, ~/.pi/agents)
|
|
352
|
+
* 4. Built-in repository/package agents directory (<rootDir>/agents)
|
|
353
|
+
*
|
|
354
|
+
* Duplicate paths are preserved in order of first appearance.
|
|
355
|
+
*/
|
|
356
|
+
export function resolveAgentDirectories(options = {}) {
|
|
357
|
+
const dirs = [];
|
|
358
|
+
const add = (dir) => {
|
|
359
|
+
if (!dir)
|
|
360
|
+
return;
|
|
361
|
+
const trimmed = dir.trim();
|
|
362
|
+
if (!trimmed)
|
|
363
|
+
return;
|
|
364
|
+
const expanded = expandTilde(trimmed);
|
|
365
|
+
const resolved = resolve(expanded);
|
|
366
|
+
if (!dirs.includes(resolved)) {
|
|
367
|
+
dirs.push(resolved);
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
if (options.customDirs) {
|
|
371
|
+
for (const d of options.customDirs)
|
|
372
|
+
add(d);
|
|
345
373
|
}
|
|
374
|
+
const env = options.env ?? process.env;
|
|
375
|
+
if (env.SUPER_PI_AGENTS_DIR) {
|
|
376
|
+
add(env.SUPER_PI_AGENTS_DIR);
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
try {
|
|
380
|
+
const home = homedir();
|
|
381
|
+
add(join(home, ".super_pi", "agents"));
|
|
382
|
+
add(join(home, ".super-pi", "agents"));
|
|
383
|
+
add(join(home, ".pi", "agents"));
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
// homedir() might throw in restricted sandbox environments
|
|
387
|
+
}
|
|
388
|
+
const root = options.rootDir ?? resolve(import.meta.dirname, "../..");
|
|
389
|
+
add(join(root, "agents"));
|
|
390
|
+
}
|
|
391
|
+
return dirs;
|
|
392
|
+
}
|
|
393
|
+
export async function readAgents(directory, options = {}) {
|
|
394
|
+
const mode = options.mode ?? "primary";
|
|
395
|
+
const rawDirs = Array.isArray(directory) ? directory : [directory];
|
|
396
|
+
const dirs = rawDirs.filter(Boolean);
|
|
346
397
|
const agents = [];
|
|
347
398
|
const problems = [];
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
399
|
+
const seenIds = new Set();
|
|
400
|
+
for (const dir of dirs) {
|
|
401
|
+
let entries;
|
|
402
|
+
try {
|
|
403
|
+
entries = await readdir(dir);
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
if (error.code === "ENOENT")
|
|
407
|
+
continue;
|
|
408
|
+
throw error;
|
|
409
|
+
}
|
|
410
|
+
for (const file of entries.filter((name) => name.toLowerCase().endsWith(".md")).sort()) {
|
|
411
|
+
const source = await readFile(join(dir, file), "utf8");
|
|
412
|
+
const parsed = parseDefinition(file, source);
|
|
413
|
+
if ("message" in parsed) {
|
|
414
|
+
const fileId = file.replace(/\.md$/i, "");
|
|
415
|
+
if (!seenIds.has(fileId)) {
|
|
416
|
+
problems.push({ file, message: parsed.message });
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
if (!seenIds.has(parsed.id)) {
|
|
421
|
+
seenIds.add(parsed.id);
|
|
422
|
+
agents.push({ ...parsed, sourceDir: dir });
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
355
426
|
}
|
|
356
427
|
// The half that was NOT asked for is filtered out here rather than marked in the UI: sub-agents are
|
|
357
428
|
// invoked BY an agent and never chosen by a human, so listing one in the picker would promise a role
|
|
358
429
|
// the runtime cannot honour. The remainder is alphabetical by id, so either roster reads as a list
|
|
359
430
|
// rather than as a directory listing.
|
|
360
|
-
return {
|
|
431
|
+
return {
|
|
432
|
+
agents: agents.filter((agent) => agent.mode === mode).sort((a, b) => a.id.localeCompare(b.id)),
|
|
433
|
+
problems,
|
|
434
|
+
};
|
|
361
435
|
}
|
|
362
436
|
/** Only `#rgb` and `#rrggbb`. A `color:` frontmatter value is rendered as a CSS background, so it has to
|
|
363
437
|
* be a colour and nothing else - accepting an arbitrary string would put author-controlled text into a
|
|
@@ -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();
|
|
@@ -4,6 +4,7 @@ import type { SubagentRunRecorder } from "../subagent-runs.js";
|
|
|
4
4
|
import type { Conversation, Message, MessageImage, PiQueueSnapshot, Workspace } from "../types.js";
|
|
5
5
|
import type { EventBroadcaster } from "../server-context.js";
|
|
6
6
|
import type { ExtensionUiService } from "./extension-ui-service.js";
|
|
7
|
+
import type { WorkflowEngine } from "../workflow-engine.js";
|
|
7
8
|
export interface SubagentDispatchRequestBody {
|
|
8
9
|
conversationId?: string;
|
|
9
10
|
subagent_type?: string;
|
|
@@ -14,6 +15,23 @@ export interface SubagentDispatchRequestBody {
|
|
|
14
15
|
description?: string;
|
|
15
16
|
loopId?: string;
|
|
16
17
|
role?: string;
|
|
18
|
+
/** The per-turn dispatch capability, presented by the primary agent's extension. See `capability.ts`. */
|
|
19
|
+
capability?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A whole orchestrated plan, as it arrives over the wire.
|
|
23
|
+
*
|
|
24
|
+
* Deliberately NOT a `WorkflowPlan`: the plan type is what validation PRODUces, and accepting it as an
|
|
25
|
+
* input would let a caller assert that its steps were already checked. This is the untrusted shape, and
|
|
26
|
+
* `validateWorkflowPlan` is what turns one into a plan.
|
|
27
|
+
*/
|
|
28
|
+
export interface WorkflowRunRequestBody {
|
|
29
|
+
conversationId?: string;
|
|
30
|
+
summary?: string;
|
|
31
|
+
onFailure?: string;
|
|
32
|
+
steps?: unknown;
|
|
33
|
+
/** The per-turn dispatch capability, presented by the primary agent's extension. See `capability.ts`. */
|
|
34
|
+
capability?: string;
|
|
17
35
|
}
|
|
18
36
|
/**
|
|
19
37
|
* Service managing execution of conversation turns, message queueing,
|
|
@@ -25,8 +43,11 @@ export declare class RunService {
|
|
|
25
43
|
private readonly subagents;
|
|
26
44
|
private readonly broadcaster;
|
|
27
45
|
private readonly extensionUiService;
|
|
46
|
+
private agentsDir;
|
|
47
|
+
private readonly workflows;
|
|
28
48
|
private readonly activeRuns;
|
|
29
|
-
constructor(store: Store, runtime: AgentRuntime, subagents: SubagentRunRecorder, broadcaster: EventBroadcaster, extensionUiService: ExtensionUiService);
|
|
49
|
+
constructor(store: Store, runtime: AgentRuntime, subagents: SubagentRunRecorder, broadcaster: EventBroadcaster, extensionUiService: ExtensionUiService, agentsDir: string | string[], workflows: WorkflowEngine);
|
|
50
|
+
setAgentsDir(dir: string | string[]): void;
|
|
30
51
|
/**
|
|
31
52
|
* Resolves the conversation and workspace synchronously for a run.
|
|
32
53
|
*/
|
|
@@ -104,10 +125,37 @@ export declare class RunService {
|
|
|
104
125
|
status: number;
|
|
105
126
|
body: unknown;
|
|
106
127
|
}>;
|
|
128
|
+
/**
|
|
129
|
+
* Validates and executes one orchestrated plan to its end.
|
|
130
|
+
*
|
|
131
|
+
* BLOCKS, unlike `dispatchTask`'s background path, and that asymmetry is the whole design. A background
|
|
132
|
+
* dispatch returns immediately because the model is expected to keep working meanwhile; a plan is the
|
|
133
|
+
* model handing the work over and wanting the chain back, so the request stays open until the last step
|
|
134
|
+
* settles. The cost is a long-held connection - a three-step pipeline is minutes - which is the same
|
|
135
|
+
* cost `task` already carries, and is bounded by the same AbortSignal the extension already forwards,
|
|
136
|
+
* plus the wall-clock cap below.
|
|
137
|
+
*
|
|
138
|
+
* 200 FOR A PLAN THAT RAN, 422 FOR ONE THAT SETTLED AS SOMETHING ELSE - `completed` including deliberate
|
|
139
|
+
* skips, and anything else the terminal status can be. Same rule `dispatchTask` applies to a single run,
|
|
140
|
+
* applied to a whole plan: the transport reports whether the work concluded, and the body says how.
|
|
141
|
+
*/
|
|
142
|
+
runWorkflow(body: WorkflowRunRequestBody): Promise<{
|
|
143
|
+
status: number;
|
|
144
|
+
body: unknown;
|
|
145
|
+
}>;
|
|
107
146
|
/**
|
|
108
147
|
* Stops a specific subagent run by runId.
|
|
109
148
|
*/
|
|
110
149
|
stopSubagent(runId: string): void;
|
|
150
|
+
/**
|
|
151
|
+
* Stops one orchestrated plan, and says whether there was anything to stop.
|
|
152
|
+
*
|
|
153
|
+
* REACHES THE CHILD, not just the loop: the engine dispatches every step under one controller's signal,
|
|
154
|
+
* so aborting it reaches the child Pi and `killTree`s it exactly as the run recorder's stop does. A stop
|
|
155
|
+
* that only ended the loop would leave the sub-agent still running and still holding the workspace
|
|
156
|
+
* directory, which is the precise gap the run card's own stop button was added to close.
|
|
157
|
+
*/
|
|
158
|
+
stopWorkflow(workflowId: string): boolean;
|
|
111
159
|
/**
|
|
112
160
|
* Stops all subagent runs belonging to a conversation.
|
|
113
161
|
*/
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { checkSubagentDepth, parentDeniedTools } from "../task-tool.js";
|
|
1
|
+
import { checkSubagentDepth, parentDeniedTools, subagentRoster } from "../task-tool.js";
|
|
2
2
|
import { newSubagentRunId } from "../subagents.js";
|
|
3
|
+
import { readAgents } from "../agents.js";
|
|
4
|
+
import { verifyCapability } from "../capability.js";
|
|
5
|
+
import { validateWorkflowPlan, workflowSummary } from "../workflow.js";
|
|
3
6
|
/**
|
|
4
7
|
* Service managing execution of conversation turns, message queueing,
|
|
5
8
|
* follow-ups, and sub-agent dispatching.
|
|
@@ -10,13 +13,20 @@ export class RunService {
|
|
|
10
13
|
subagents;
|
|
11
14
|
broadcaster;
|
|
12
15
|
extensionUiService;
|
|
16
|
+
agentsDir;
|
|
17
|
+
workflows;
|
|
13
18
|
activeRuns = new Map();
|
|
14
|
-
constructor(store, runtime, subagents, broadcaster, extensionUiService) {
|
|
19
|
+
constructor(store, runtime, subagents, broadcaster, extensionUiService, agentsDir, workflows) {
|
|
15
20
|
this.store = store;
|
|
16
21
|
this.runtime = runtime;
|
|
17
22
|
this.subagents = subagents;
|
|
18
23
|
this.broadcaster = broadcaster;
|
|
19
24
|
this.extensionUiService = extensionUiService;
|
|
25
|
+
this.agentsDir = agentsDir;
|
|
26
|
+
this.workflows = workflows;
|
|
27
|
+
}
|
|
28
|
+
setAgentsDir(dir) {
|
|
29
|
+
this.agentsDir = dir;
|
|
20
30
|
}
|
|
21
31
|
/**
|
|
22
32
|
* Resolves the conversation and workspace synchronously for a run.
|
|
@@ -63,6 +73,12 @@ export class RunService {
|
|
|
63
73
|
if (this.subagents.stopConversation(conversationId)) {
|
|
64
74
|
console.log(`Super Pi: stopped the sub-agent runs of conversation ${conversationId}.`);
|
|
65
75
|
}
|
|
76
|
+
// Plans too, and separately: a plan's own loop holds the signal that reaches its current child, so
|
|
77
|
+
// stopping only the runs would let it dispatch the NEXT step a moment later. Reported on its own line
|
|
78
|
+
// because a person who pressed stop wants to know a plan was still marching, not just a card.
|
|
79
|
+
if (this.workflows.stopConversation(conversationId)) {
|
|
80
|
+
console.log(`Super Pi: stopped the running orchestration plans of conversation ${conversationId}.`);
|
|
81
|
+
}
|
|
66
82
|
this.broadcaster.broadcastState();
|
|
67
83
|
}
|
|
68
84
|
/**
|
|
@@ -258,6 +274,12 @@ export class RunService {
|
|
|
258
274
|
if (!conversationId || !subagentType || !task) {
|
|
259
275
|
throw new Error("task requires conversationId, subagent_type and prompt");
|
|
260
276
|
}
|
|
277
|
+
// BEFORE anything else, including before the conversation is looked up: a request that cannot prove it
|
|
278
|
+
// came from this server's primary agent gets no answer about what exists. Everything below this line
|
|
279
|
+
// is about shaping a dispatch; this line is about whether the caller may have one at all.
|
|
280
|
+
const capability = verifyCapability(conversationId, body.capability);
|
|
281
|
+
if (!capability.ok)
|
|
282
|
+
throw new Error(capability.message);
|
|
261
283
|
if (body.depth !== undefined)
|
|
262
284
|
checkSubagentDepth(body.depth);
|
|
263
285
|
const { conversation, cwd } = this.dispatchContext(conversationId);
|
|
@@ -326,12 +348,69 @@ export class RunService {
|
|
|
326
348
|
},
|
|
327
349
|
};
|
|
328
350
|
}
|
|
351
|
+
/**
|
|
352
|
+
* Validates and executes one orchestrated plan to its end.
|
|
353
|
+
*
|
|
354
|
+
* BLOCKS, unlike `dispatchTask`'s background path, and that asymmetry is the whole design. A background
|
|
355
|
+
* dispatch returns immediately because the model is expected to keep working meanwhile; a plan is the
|
|
356
|
+
* model handing the work over and wanting the chain back, so the request stays open until the last step
|
|
357
|
+
* settles. The cost is a long-held connection - a three-step pipeline is minutes - which is the same
|
|
358
|
+
* cost `task` already carries, and is bounded by the same AbortSignal the extension already forwards,
|
|
359
|
+
* plus the wall-clock cap below.
|
|
360
|
+
*
|
|
361
|
+
* 200 FOR A PLAN THAT RAN, 422 FOR ONE THAT SETTLED AS SOMETHING ELSE - `completed` including deliberate
|
|
362
|
+
* skips, and anything else the terminal status can be. Same rule `dispatchTask` applies to a single run,
|
|
363
|
+
* applied to a whole plan: the transport reports whether the work concluded, and the body says how.
|
|
364
|
+
*/
|
|
365
|
+
async runWorkflow(body) {
|
|
366
|
+
const conversationId = body.conversationId?.trim();
|
|
367
|
+
if (!conversationId)
|
|
368
|
+
throw new Error("workflow requires conversationId");
|
|
369
|
+
// Same rule as `dispatchTask`, and for a larger reason: this endpoint triggers a SEQUENCE of
|
|
370
|
+
// dispatches, so a caller that cannot prove it is the primary agent would get an unattended pipeline
|
|
371
|
+
// of sub-agents writing to the workspace rather than one delegation.
|
|
372
|
+
const capability = verifyCapability(conversationId, body.capability);
|
|
373
|
+
if (!capability.ok)
|
|
374
|
+
throw new Error(capability.message);
|
|
375
|
+
const { conversation, cwd } = this.dispatchContext(conversationId);
|
|
376
|
+
// Validated against the roster THIS CALL reads from disk, for the same freshness reason the task tool
|
|
377
|
+
// builds its roster per turn: a sub-agent added while the server is running is dispatchable on the next
|
|
378
|
+
// message with no restart, because a person edits `agents/*.md` while the server runs.
|
|
379
|
+
const { agents } = await readAgents(this.agentsDir, { mode: "subagent" });
|
|
380
|
+
const validated = validateWorkflowPlan(body, subagentRoster(agents));
|
|
381
|
+
if (!validated.plan)
|
|
382
|
+
throw new Error(validated.refusal);
|
|
383
|
+
const { workflow } = await this.workflows.run({
|
|
384
|
+
conversationId: conversation.id,
|
|
385
|
+
cwd,
|
|
386
|
+
plan: validated.plan,
|
|
387
|
+
...(conversation.model ? { model: conversation.model } : {}),
|
|
388
|
+
// The same inheritance a single dispatch applies: a parent's DENY reaches its children, its
|
|
389
|
+
// allowlist does not. Every step gets the primary's denials, exactly as one dispatch would.
|
|
390
|
+
denyTools: parentDeniedTools(await this.runtime.agentFor?.(conversation)),
|
|
391
|
+
});
|
|
392
|
+
return {
|
|
393
|
+
status: workflow.status === "completed" ? 200 : 422,
|
|
394
|
+
body: { workflow: workflowSummary(workflow) },
|
|
395
|
+
};
|
|
396
|
+
}
|
|
329
397
|
/**
|
|
330
398
|
* Stops a specific subagent run by runId.
|
|
331
399
|
*/
|
|
332
400
|
stopSubagent(runId) {
|
|
333
401
|
this.subagents.stop(runId);
|
|
334
402
|
}
|
|
403
|
+
/**
|
|
404
|
+
* Stops one orchestrated plan, and says whether there was anything to stop.
|
|
405
|
+
*
|
|
406
|
+
* REACHES THE CHILD, not just the loop: the engine dispatches every step under one controller's signal,
|
|
407
|
+
* so aborting it reaches the child Pi and `killTree`s it exactly as the run recorder's stop does. A stop
|
|
408
|
+
* that only ended the loop would leave the sub-agent still running and still holding the workspace
|
|
409
|
+
* directory, which is the precise gap the run card's own stop button was added to close.
|
|
410
|
+
*/
|
|
411
|
+
stopWorkflow(workflowId) {
|
|
412
|
+
return this.workflows.stop(workflowId);
|
|
413
|
+
}
|
|
335
414
|
/**
|
|
336
415
|
* Stops all subagent runs belonging to a conversation.
|
|
337
416
|
*/
|
|
@@ -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
|
}
|