llm-party-cli 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +12 -2
- package/package.json +1 -1
- package/dist/adapters/base.js +0 -1
- package/dist/adapters/claude.js +0 -64
- package/dist/adapters/codex.js +0 -44
- package/dist/adapters/copilot.js +0 -46
- package/dist/adapters/glm.js +0 -91
- package/dist/config/loader.js +0 -131
- package/dist/llm-party +0 -0
- package/dist/orchestrator.js +0 -185
- package/dist/types.js +0 -1
- package/dist/ui/terminal.js +0 -219
package/dist/index.js
CHANGED
|
@@ -76490,8 +76490,10 @@ function detectBinary(command) {
|
|
|
76490
76490
|
const proc = spawn4(command, ["--version"], {
|
|
76491
76491
|
stdio: ["ignore", "pipe", "ignore"],
|
|
76492
76492
|
shell: true,
|
|
76493
|
-
timeout: DETECT_TIMEOUT
|
|
76493
|
+
timeout: DETECT_TIMEOUT,
|
|
76494
|
+
detached: true
|
|
76494
76495
|
});
|
|
76496
|
+
proc.unref();
|
|
76495
76497
|
let output = "";
|
|
76496
76498
|
proc.stdout?.on("data", (chunk) => {
|
|
76497
76499
|
output += chunk.toString();
|
|
@@ -76516,8 +76518,10 @@ function detectAlias(command) {
|
|
|
76516
76518
|
const timer = setTimeout(() => resolve4({ available: false }), DETECT_TIMEOUT);
|
|
76517
76519
|
const proc = spawn4(shell, ["-ic", `type ${command}`], {
|
|
76518
76520
|
stdio: ["ignore", "pipe", "ignore"],
|
|
76519
|
-
timeout: DETECT_TIMEOUT
|
|
76521
|
+
timeout: DETECT_TIMEOUT,
|
|
76522
|
+
detached: true
|
|
76520
76523
|
});
|
|
76524
|
+
proc.unref();
|
|
76521
76525
|
proc.on("close", (code) => {
|
|
76522
76526
|
clearTimeout(timer);
|
|
76523
76527
|
resolve4({ available: code === 0 });
|
|
@@ -76798,6 +76802,9 @@ function ConfigWizard({ isFirstRun, onComplete, onCancel, existingConfig }) {
|
|
|
76798
76802
|
detectProviders().then((results) => {
|
|
76799
76803
|
setDetection(results);
|
|
76800
76804
|
setStep("providers");
|
|
76805
|
+
}).catch(() => {
|
|
76806
|
+
setDetection([]);
|
|
76807
|
+
setStep("providers");
|
|
76801
76808
|
});
|
|
76802
76809
|
}, []);
|
|
76803
76810
|
const existingByProvider = new Map((existingConfig?.agents || []).map((a2) => [a2.provider, a2]));
|
|
@@ -76902,6 +76909,9 @@ function ConfigWizard({ isFirstRun, onComplete, onCancel, existingConfig }) {
|
|
|
76902
76909
|
}, [selectedIds, existingConfig]);
|
|
76903
76910
|
useKeyboard((key) => {
|
|
76904
76911
|
if (step !== "configure") {
|
|
76912
|
+
if (step === "detect") {
|
|
76913
|
+
return;
|
|
76914
|
+
}
|
|
76905
76915
|
if (step === "done") {
|
|
76906
76916
|
if (key.name === "enter" || key.name === "return" || key.name === "space") {
|
|
76907
76917
|
onComplete();
|
package/package.json
CHANGED
package/dist/adapters/base.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/adapters/claude.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
-
export class ClaudeAdapter {
|
|
3
|
-
name;
|
|
4
|
-
provider = "claude";
|
|
5
|
-
model;
|
|
6
|
-
systemPrompt = "";
|
|
7
|
-
sessionId = "";
|
|
8
|
-
runtimeEnv = {};
|
|
9
|
-
claudeExecutable;
|
|
10
|
-
constructor(name, model) {
|
|
11
|
-
this.name = name;
|
|
12
|
-
this.model = model;
|
|
13
|
-
}
|
|
14
|
-
async init(config) {
|
|
15
|
-
this.systemPrompt = config.resolvedPrompt ?? "";
|
|
16
|
-
this.runtimeEnv = { ...process.env, ...(config.env ?? {}) };
|
|
17
|
-
this.claudeExecutable = config.executablePath ?? process.env.CLAUDE_CODE_EXECUTABLE;
|
|
18
|
-
}
|
|
19
|
-
async send(messages) {
|
|
20
|
-
const transcript = messages
|
|
21
|
-
.map((m) => `[${m.from}]: ${m.text}`)
|
|
22
|
-
.join("\n\n");
|
|
23
|
-
return await this.queryClaude(transcript);
|
|
24
|
-
}
|
|
25
|
-
async destroy() {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
async queryClaude(transcript) {
|
|
29
|
-
const executableOpt = this.claudeExecutable
|
|
30
|
-
? { pathToClaudeCodeExecutable: this.claudeExecutable }
|
|
31
|
-
: {};
|
|
32
|
-
const options = {
|
|
33
|
-
cwd: process.cwd(),
|
|
34
|
-
env: this.runtimeEnv,
|
|
35
|
-
allowedTools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"],
|
|
36
|
-
permissionMode: "bypassPermissions",
|
|
37
|
-
allowDangerouslySkipPermissions: true,
|
|
38
|
-
systemPrompt: this.systemPrompt,
|
|
39
|
-
model: this.model,
|
|
40
|
-
settingSources: [],
|
|
41
|
-
...(this.sessionId ? { resume: this.sessionId } : {}),
|
|
42
|
-
...executableOpt
|
|
43
|
-
};
|
|
44
|
-
for await (const message of query({ prompt: transcript, options })) {
|
|
45
|
-
if (message &&
|
|
46
|
-
typeof message === "object" &&
|
|
47
|
-
"type" in message &&
|
|
48
|
-
"subtype" in message &&
|
|
49
|
-
"session_id" in message &&
|
|
50
|
-
message.type === "system" &&
|
|
51
|
-
message.subtype === "init" &&
|
|
52
|
-
typeof message.session_id === "string") {
|
|
53
|
-
this.sessionId = message.session_id;
|
|
54
|
-
}
|
|
55
|
-
if (message && typeof message === "object" && "result" in message) {
|
|
56
|
-
const result = message.result;
|
|
57
|
-
return typeof result === "string" && result.length > 0
|
|
58
|
-
? result
|
|
59
|
-
: "[No text response from Claude]";
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return "[No text response from Claude]";
|
|
63
|
-
}
|
|
64
|
-
}
|
package/dist/adapters/codex.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { Codex } from "@openai/codex-sdk";
|
|
2
|
-
export class CodexAdapter {
|
|
3
|
-
name;
|
|
4
|
-
provider = "codex";
|
|
5
|
-
model;
|
|
6
|
-
codex;
|
|
7
|
-
thread;
|
|
8
|
-
constructor(name, model) {
|
|
9
|
-
this.name = name;
|
|
10
|
-
this.model = model;
|
|
11
|
-
}
|
|
12
|
-
async init(config) {
|
|
13
|
-
const cliPath = config.executablePath ?? process.env.CODEX_CLI_EXECUTABLE;
|
|
14
|
-
const systemPrompt = config.resolvedPrompt ?? "";
|
|
15
|
-
this.codex = new Codex({
|
|
16
|
-
...(cliPath ? { codexPathOverride: cliPath } : {}),
|
|
17
|
-
...(config.env?.OPENAI_API_KEY ? { apiKey: config.env.OPENAI_API_KEY } : {}),
|
|
18
|
-
...(systemPrompt ? { config: { developer_instructions: systemPrompt } } : {}),
|
|
19
|
-
});
|
|
20
|
-
this.thread = this.codex.startThread({
|
|
21
|
-
model: this.model,
|
|
22
|
-
sandboxMode: "danger-full-access",
|
|
23
|
-
workingDirectory: process.cwd(),
|
|
24
|
-
approvalPolicy: "never",
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
async send(messages) {
|
|
28
|
-
if (!this.thread) {
|
|
29
|
-
return "[Codex thread not initialized]";
|
|
30
|
-
}
|
|
31
|
-
const transcript = messages
|
|
32
|
-
.map((m) => `[${m.from}]: ${m.text}`)
|
|
33
|
-
.join("\n\n");
|
|
34
|
-
const turn = await this.thread.run(transcript);
|
|
35
|
-
if (turn.finalResponse && turn.finalResponse.length > 0) {
|
|
36
|
-
return turn.finalResponse;
|
|
37
|
-
}
|
|
38
|
-
return "[No text response from Codex]";
|
|
39
|
-
}
|
|
40
|
-
async destroy() {
|
|
41
|
-
this.thread = undefined;
|
|
42
|
-
this.codex = undefined;
|
|
43
|
-
}
|
|
44
|
-
}
|
package/dist/adapters/copilot.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { CopilotClient, approveAll } from "@github/copilot-sdk";
|
|
2
|
-
export class CopilotAdapter {
|
|
3
|
-
name;
|
|
4
|
-
provider = "copilot";
|
|
5
|
-
model;
|
|
6
|
-
client;
|
|
7
|
-
session;
|
|
8
|
-
constructor(name, model) {
|
|
9
|
-
this.name = name;
|
|
10
|
-
this.model = model;
|
|
11
|
-
}
|
|
12
|
-
async init(config) {
|
|
13
|
-
const systemPrompt = config.resolvedPrompt ?? "";
|
|
14
|
-
const cliPath = config.executablePath ?? process.env.COPILOT_CLI_EXECUTABLE;
|
|
15
|
-
this.client = new CopilotClient({
|
|
16
|
-
...(cliPath ? { cliPath } : {}),
|
|
17
|
-
});
|
|
18
|
-
await this.client.start();
|
|
19
|
-
this.session = await this.client.createSession({
|
|
20
|
-
model: this.model,
|
|
21
|
-
systemMessage: { content: systemPrompt },
|
|
22
|
-
onPermissionRequest: approveAll,
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
async send(messages) {
|
|
26
|
-
if (!this.session) {
|
|
27
|
-
return "[Copilot session not initialized]";
|
|
28
|
-
}
|
|
29
|
-
const transcript = messages
|
|
30
|
-
.map((m) => `[${m.from}]: ${m.text}`)
|
|
31
|
-
.join("\n\n");
|
|
32
|
-
const response = await this.session.sendAndWait({ prompt: transcript });
|
|
33
|
-
if (response && response.data && typeof response.data.content === "string" && response.data.content.length > 0) {
|
|
34
|
-
return response.data.content;
|
|
35
|
-
}
|
|
36
|
-
return "[No text response from Copilot]";
|
|
37
|
-
}
|
|
38
|
-
async destroy() {
|
|
39
|
-
if (this.session) {
|
|
40
|
-
await this.session.disconnect();
|
|
41
|
-
}
|
|
42
|
-
if (this.client) {
|
|
43
|
-
await this.client.stop();
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
package/dist/adapters/glm.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
3
|
-
export class GlmAdapter {
|
|
4
|
-
name;
|
|
5
|
-
provider = "glm";
|
|
6
|
-
model;
|
|
7
|
-
systemPrompt = "";
|
|
8
|
-
sessionId = "";
|
|
9
|
-
runtimeEnv = {};
|
|
10
|
-
claudeExecutable;
|
|
11
|
-
constructor(name, model) {
|
|
12
|
-
this.name = name;
|
|
13
|
-
this.model = model;
|
|
14
|
-
}
|
|
15
|
-
async init(config) {
|
|
16
|
-
this.systemPrompt = config.resolvedPrompt ?? "";
|
|
17
|
-
const aliasEnv = await loadGlmAliasEnv();
|
|
18
|
-
this.runtimeEnv = { ...process.env, ...aliasEnv, ...(config.env ?? {}) };
|
|
19
|
-
this.claudeExecutable = config.executablePath ?? process.env.CLAUDE_CODE_EXECUTABLE;
|
|
20
|
-
}
|
|
21
|
-
async send(messages) {
|
|
22
|
-
const transcript = messages
|
|
23
|
-
.map((m) => `[${m.from}]: ${m.text}`)
|
|
24
|
-
.join("\n\n");
|
|
25
|
-
return await this.queryGlm(transcript);
|
|
26
|
-
}
|
|
27
|
-
async destroy() {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
async queryGlm(transcript) {
|
|
31
|
-
const executableOpt = this.claudeExecutable
|
|
32
|
-
? { pathToClaudeCodeExecutable: this.claudeExecutable }
|
|
33
|
-
: {};
|
|
34
|
-
const options = {
|
|
35
|
-
cwd: process.cwd(),
|
|
36
|
-
env: this.runtimeEnv,
|
|
37
|
-
allowedTools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"],
|
|
38
|
-
permissionMode: "bypassPermissions",
|
|
39
|
-
allowDangerouslySkipPermissions: true,
|
|
40
|
-
systemPrompt: this.systemPrompt,
|
|
41
|
-
model: this.model,
|
|
42
|
-
settingSources: [],
|
|
43
|
-
...(this.sessionId ? { resume: this.sessionId } : {}),
|
|
44
|
-
...executableOpt
|
|
45
|
-
};
|
|
46
|
-
for await (const message of query({ prompt: transcript, options })) {
|
|
47
|
-
if (message &&
|
|
48
|
-
typeof message === "object" &&
|
|
49
|
-
"type" in message &&
|
|
50
|
-
"subtype" in message &&
|
|
51
|
-
"session_id" in message &&
|
|
52
|
-
message.type === "system" &&
|
|
53
|
-
message.subtype === "init" &&
|
|
54
|
-
typeof message.session_id === "string") {
|
|
55
|
-
this.sessionId = message.session_id;
|
|
56
|
-
}
|
|
57
|
-
if (message && typeof message === "object" && "result" in message) {
|
|
58
|
-
const result = message.result;
|
|
59
|
-
return typeof result === "string" && result.length > 0
|
|
60
|
-
? result
|
|
61
|
-
: "[No text response from GLM]";
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return "[No text response from GLM]";
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
async function loadGlmAliasEnv() {
|
|
68
|
-
return new Promise((resolve) => {
|
|
69
|
-
const child = spawn("zsh", ["-ic", "alias glm"], {
|
|
70
|
-
cwd: process.cwd(),
|
|
71
|
-
env: process.env,
|
|
72
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
73
|
-
});
|
|
74
|
-
let stdout = "";
|
|
75
|
-
child.stdout.on("data", (chunk) => {
|
|
76
|
-
stdout += String(chunk);
|
|
77
|
-
});
|
|
78
|
-
child.on("close", () => {
|
|
79
|
-
const env = {};
|
|
80
|
-
const tokens = stdout.match(/[A-Z_]+="[^"]*"/g) ?? [];
|
|
81
|
-
for (const token of tokens) {
|
|
82
|
-
const [key, raw] = token.split("=");
|
|
83
|
-
env[key] = raw.replace(/^"|"$/g, "");
|
|
84
|
-
}
|
|
85
|
-
resolve(env);
|
|
86
|
-
});
|
|
87
|
-
child.on("error", () => {
|
|
88
|
-
resolve({});
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
}
|
package/dist/config/loader.js
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { readFile, writeFile, access, mkdir } from "node:fs/promises";
|
|
2
|
-
import { homedir, userInfo } from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
const VALID_PROVIDERS = ["claude", "codex", "copilot", "glm"];
|
|
5
|
-
const LLM_PARTY_HOME = path.join(homedir(), ".llm-party");
|
|
6
|
-
function validateConfig(data) {
|
|
7
|
-
if (!data || typeof data !== "object") {
|
|
8
|
-
throw new Error("Config must be an object");
|
|
9
|
-
}
|
|
10
|
-
const cfg = data;
|
|
11
|
-
if (!Array.isArray(cfg.agents)) {
|
|
12
|
-
throw new Error("Config must have 'agents' array");
|
|
13
|
-
}
|
|
14
|
-
if (cfg.agents.length === 0) {
|
|
15
|
-
throw new Error("Config 'agents' array cannot be empty");
|
|
16
|
-
}
|
|
17
|
-
for (let i = 0; i < cfg.agents.length; i++) {
|
|
18
|
-
const agent = cfg.agents[i];
|
|
19
|
-
if (!agent || typeof agent !== "object") {
|
|
20
|
-
throw new Error(`Agent at index ${i} must be an object`);
|
|
21
|
-
}
|
|
22
|
-
if (typeof agent.name !== "string" || agent.name.trim() === "") {
|
|
23
|
-
throw new Error(`Agent at index ${i} must have a non-empty 'name' string`);
|
|
24
|
-
}
|
|
25
|
-
if (typeof agent.model !== "string" || agent.model.trim() === "") {
|
|
26
|
-
throw new Error(`Agent '${agent.name}' must have a non-empty 'model' string`);
|
|
27
|
-
}
|
|
28
|
-
if (!VALID_PROVIDERS.includes(agent.provider)) {
|
|
29
|
-
throw new Error(`Agent '${agent.name}' has invalid provider '${agent.provider}'. Valid: ${VALID_PROVIDERS.join(", ")}`);
|
|
30
|
-
}
|
|
31
|
-
if (agent.prompts !== undefined) {
|
|
32
|
-
const isArray = Array.isArray(agent.prompts) && agent.prompts.every((p) => typeof p === "string");
|
|
33
|
-
if (!isArray) {
|
|
34
|
-
throw new Error(`Agent '${agent.name}' prompts must be a string array`);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
for (const agent of cfg.agents) {
|
|
39
|
-
if (typeof agent.executablePath === "string" && agent.executablePath.startsWith("~/")) {
|
|
40
|
-
agent.executablePath = homedir() + agent.executablePath.slice(1);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
if (!cfg.humanName || (typeof cfg.humanName === "string" && cfg.humanName.trim() === "")) {
|
|
44
|
-
cfg.humanName = userInfo().username || "USER";
|
|
45
|
-
}
|
|
46
|
-
return cfg;
|
|
47
|
-
}
|
|
48
|
-
export async function resolveConfigPath(appRoot) {
|
|
49
|
-
if (process.env.LLM_PARTY_CONFIG) {
|
|
50
|
-
return path.resolve(process.env.LLM_PARTY_CONFIG);
|
|
51
|
-
}
|
|
52
|
-
const globalConfig = path.join(LLM_PARTY_HOME, "config.json");
|
|
53
|
-
try {
|
|
54
|
-
await access(globalConfig);
|
|
55
|
-
return globalConfig;
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
// fall through to package default
|
|
59
|
-
}
|
|
60
|
-
return path.join(appRoot, "configs", "default.json");
|
|
61
|
-
}
|
|
62
|
-
export async function resolveBasePrompt(appRoot) {
|
|
63
|
-
const bundledBase = path.join(appRoot, "prompts", "base.md");
|
|
64
|
-
return await readFile(bundledBase, "utf8");
|
|
65
|
-
}
|
|
66
|
-
export async function resolveArtifactsPrompt(appRoot) {
|
|
67
|
-
const bundledArtifacts = path.join(appRoot, "prompts", "artifacts.md");
|
|
68
|
-
return await readFile(bundledArtifacts, "utf8");
|
|
69
|
-
}
|
|
70
|
-
export async function initProjectFolder(cwd) {
|
|
71
|
-
const projectHome = path.join(cwd, ".llm-party");
|
|
72
|
-
const memoryDir = path.join(projectHome, "memory");
|
|
73
|
-
const skillsDir = path.join(projectHome, "skills");
|
|
74
|
-
await mkdir(memoryDir, { recursive: true });
|
|
75
|
-
await mkdir(skillsDir, { recursive: true });
|
|
76
|
-
const tasksFile = path.join(projectHome, "TASKS.md");
|
|
77
|
-
try {
|
|
78
|
-
await access(tasksFile);
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
await writeFile(tasksFile, "# Tasks\n", "utf8");
|
|
82
|
-
}
|
|
83
|
-
const projectMd = path.join(memoryDir, "project.md");
|
|
84
|
-
try {
|
|
85
|
-
await access(projectMd);
|
|
86
|
-
}
|
|
87
|
-
catch {
|
|
88
|
-
await writeFile(projectMd, "# Project Memory\n\n## Current State\n\nLast Updated:\nActive:\nBlockers:\nNext:\n\n---\n\n## Log\n", "utf8");
|
|
89
|
-
}
|
|
90
|
-
const decisionsMd = path.join(memoryDir, "decisions.md");
|
|
91
|
-
try {
|
|
92
|
-
await access(decisionsMd);
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
await writeFile(decisionsMd, "# Decisions\n", "utf8");
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
export async function initLlmPartyHome(appRoot) {
|
|
99
|
-
await mkdir(LLM_PARTY_HOME, { recursive: true });
|
|
100
|
-
await mkdir(path.join(LLM_PARTY_HOME, "sessions"), { recursive: true });
|
|
101
|
-
await mkdir(path.join(LLM_PARTY_HOME, "network"), { recursive: true });
|
|
102
|
-
await mkdir(path.join(LLM_PARTY_HOME, "agents"), { recursive: true });
|
|
103
|
-
const projectsYml = path.join(LLM_PARTY_HOME, "network", "projects.yml");
|
|
104
|
-
try {
|
|
105
|
-
await access(projectsYml);
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
await writeFile(projectsYml, "projects: []\n", "utf8");
|
|
109
|
-
}
|
|
110
|
-
const librariesYml = path.join(LLM_PARTY_HOME, "network", "libraries.yml");
|
|
111
|
-
try {
|
|
112
|
-
await access(librariesYml);
|
|
113
|
-
}
|
|
114
|
-
catch {
|
|
115
|
-
await writeFile(librariesYml, "libraries: []\n", "utf8");
|
|
116
|
-
}
|
|
117
|
-
const globalConfig = path.join(LLM_PARTY_HOME, "config.json");
|
|
118
|
-
try {
|
|
119
|
-
await access(globalConfig);
|
|
120
|
-
}
|
|
121
|
-
catch {
|
|
122
|
-
const bundledConfig = await readFile(path.join(appRoot, "configs", "default.json"), "utf8");
|
|
123
|
-
await writeFile(globalConfig, bundledConfig, "utf8");
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
export async function loadConfig(configPath) {
|
|
127
|
-
const raw = await readFile(configPath, "utf8");
|
|
128
|
-
const parsed = JSON.parse(raw);
|
|
129
|
-
return validateConfig(parsed);
|
|
130
|
-
}
|
|
131
|
-
export { LLM_PARTY_HOME };
|
package/dist/llm-party
DELETED
|
Binary file
|
package/dist/orchestrator.js
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import { appendFile, mkdir, writeFile } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
export class Orchestrator {
|
|
4
|
-
agents;
|
|
5
|
-
agentTags;
|
|
6
|
-
conversation = [];
|
|
7
|
-
lastSeenByAgent = new Map();
|
|
8
|
-
humanName;
|
|
9
|
-
humanTag;
|
|
10
|
-
sessionId;
|
|
11
|
-
transcriptPath;
|
|
12
|
-
defaultTimeout;
|
|
13
|
-
agentTimeouts;
|
|
14
|
-
messageId = 0;
|
|
15
|
-
contextWindowSize = 16;
|
|
16
|
-
constructor(agents, humanName = "USER", agentTags, humanTag, defaultTimeout = 600000, agentTimeouts) {
|
|
17
|
-
this.agents = new Map(agents.map((agent) => [agent.name, agent]));
|
|
18
|
-
this.agentTags = new Map(agents.map((agent) => [agent.name, agentTags?.[agent.name] ?? defaultTagFor(agent.name)]));
|
|
19
|
-
this.humanName = humanName;
|
|
20
|
-
this.humanTag = humanTag ?? defaultTagFor(humanName);
|
|
21
|
-
this.defaultTimeout = defaultTimeout;
|
|
22
|
-
this.agentTimeouts = new Map(Object.entries(agentTimeouts ?? {}));
|
|
23
|
-
this.sessionId = createSessionId();
|
|
24
|
-
this.transcriptPath = path.resolve(".llm-party", "sessions", `transcript-${this.sessionId}.jsonl`);
|
|
25
|
-
for (const agent of agents) {
|
|
26
|
-
this.lastSeenByAgent.set(agent.name, 0);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
getSessionId() {
|
|
30
|
-
return this.sessionId;
|
|
31
|
-
}
|
|
32
|
-
getTranscriptPath() {
|
|
33
|
-
return this.transcriptPath;
|
|
34
|
-
}
|
|
35
|
-
getHumanName() {
|
|
36
|
-
return this.humanName;
|
|
37
|
-
}
|
|
38
|
-
getHumanTag() {
|
|
39
|
-
return this.humanTag;
|
|
40
|
-
}
|
|
41
|
-
listAgents() {
|
|
42
|
-
return Array.from(this.agents.values()).map((agent) => ({
|
|
43
|
-
name: agent.name,
|
|
44
|
-
tag: this.agentTags.get(agent.name) ?? defaultTagFor(agent.name),
|
|
45
|
-
provider: agent.provider,
|
|
46
|
-
model: agent.model
|
|
47
|
-
}));
|
|
48
|
-
}
|
|
49
|
-
addUserMessage(text) {
|
|
50
|
-
const message = {
|
|
51
|
-
id: ++this.messageId,
|
|
52
|
-
from: this.humanName,
|
|
53
|
-
text,
|
|
54
|
-
createdAt: new Date().toISOString()
|
|
55
|
-
};
|
|
56
|
-
this.conversation.push(message);
|
|
57
|
-
return message;
|
|
58
|
-
}
|
|
59
|
-
getHistory() {
|
|
60
|
-
return [...this.conversation];
|
|
61
|
-
}
|
|
62
|
-
resolveTargets(selector) {
|
|
63
|
-
const normalized = selector.trim().toLowerCase();
|
|
64
|
-
if (normalized === "all" || normalized === "everyone") {
|
|
65
|
-
return Array.from(this.agents.keys());
|
|
66
|
-
}
|
|
67
|
-
const byName = Array.from(this.agents.values())
|
|
68
|
-
.filter((agent) => {
|
|
69
|
-
const tag = this.agentTags.get(agent.name) ?? defaultTagFor(agent.name);
|
|
70
|
-
return agent.name.toLowerCase() === normalized || tag.toLowerCase() === normalized;
|
|
71
|
-
})
|
|
72
|
-
.map((agent) => agent.name);
|
|
73
|
-
if (byName.length > 0) {
|
|
74
|
-
return byName;
|
|
75
|
-
}
|
|
76
|
-
return Array.from(this.agents.values())
|
|
77
|
-
.filter((agent) => agent.provider.toLowerCase() === normalized)
|
|
78
|
-
.map((agent) => agent.name);
|
|
79
|
-
}
|
|
80
|
-
async fanOut(targetAgentNames) {
|
|
81
|
-
return this.fanOutWithProgress(targetAgentNames, () => { });
|
|
82
|
-
}
|
|
83
|
-
async fanOutWithProgress(targetAgentNames, onMessage) {
|
|
84
|
-
const requestedTargets = targetAgentNames && targetAgentNames.length > 0
|
|
85
|
-
? targetAgentNames
|
|
86
|
-
: Array.from(this.agents.keys());
|
|
87
|
-
const targets = requestedTargets
|
|
88
|
-
.map((name) => this.agents.get(name))
|
|
89
|
-
.filter((agent) => Boolean(agent));
|
|
90
|
-
const historyMaxId = this.messageId;
|
|
91
|
-
const settled = await Promise.allSettled(targets.map(async (agent) => {
|
|
92
|
-
const lastSeen = this.lastSeenByAgent.get(agent.name) ?? 0;
|
|
93
|
-
const unseen = this.conversation.filter((msg) => msg.id > lastSeen && msg.from.toUpperCase() !== agent.name.toUpperCase());
|
|
94
|
-
if (unseen.length === 0) {
|
|
95
|
-
this.lastSeenByAgent.set(agent.name, historyMaxId);
|
|
96
|
-
const response = {
|
|
97
|
-
id: ++this.messageId,
|
|
98
|
-
from: agent.name.toUpperCase(),
|
|
99
|
-
text: "[No new messages for this agent]",
|
|
100
|
-
createdAt: new Date().toISOString()
|
|
101
|
-
};
|
|
102
|
-
this.conversation.push(response);
|
|
103
|
-
await this.appendTranscript(response);
|
|
104
|
-
onMessage(response);
|
|
105
|
-
return response;
|
|
106
|
-
}
|
|
107
|
-
const inputMessages = this.buildInputForAgent(agent.name, unseen);
|
|
108
|
-
const responseText = await this.sendWithTimeout(agent, inputMessages, this.timeoutFor(agent.name));
|
|
109
|
-
const response = {
|
|
110
|
-
id: ++this.messageId,
|
|
111
|
-
from: agent.name.toUpperCase(),
|
|
112
|
-
text: responseText,
|
|
113
|
-
createdAt: new Date().toISOString()
|
|
114
|
-
};
|
|
115
|
-
this.lastSeenByAgent.set(agent.name, historyMaxId);
|
|
116
|
-
this.conversation.push(response);
|
|
117
|
-
await this.appendTranscript(response);
|
|
118
|
-
onMessage(response);
|
|
119
|
-
return response;
|
|
120
|
-
}));
|
|
121
|
-
const results = settled.map((item, idx) => {
|
|
122
|
-
if (item.status === "fulfilled") {
|
|
123
|
-
return item.value;
|
|
124
|
-
}
|
|
125
|
-
const agent = targets[idx];
|
|
126
|
-
const response = {
|
|
127
|
-
id: ++this.messageId,
|
|
128
|
-
from: agent.name.toUpperCase(),
|
|
129
|
-
text: `[Adapter Error] ${item.reason instanceof Error ? item.reason.message : String(item.reason)}`,
|
|
130
|
-
createdAt: new Date().toISOString()
|
|
131
|
-
};
|
|
132
|
-
this.lastSeenByAgent.set(agent.name, historyMaxId);
|
|
133
|
-
this.conversation.push(response);
|
|
134
|
-
void this.appendTranscript(response);
|
|
135
|
-
onMessage(response);
|
|
136
|
-
return response;
|
|
137
|
-
});
|
|
138
|
-
return results;
|
|
139
|
-
}
|
|
140
|
-
async appendTranscript(message) {
|
|
141
|
-
const transcriptDir = path.dirname(this.transcriptPath);
|
|
142
|
-
await mkdir(transcriptDir, { recursive: true });
|
|
143
|
-
await appendFile(this.transcriptPath, `${JSON.stringify(message)}\n`, "utf8");
|
|
144
|
-
}
|
|
145
|
-
async saveHistory(targetPath) {
|
|
146
|
-
await writeFile(targetPath, JSON.stringify(this.conversation, null, 2), "utf8");
|
|
147
|
-
}
|
|
148
|
-
async sendWithTimeout(agent, messages, timeoutMs) {
|
|
149
|
-
let timer;
|
|
150
|
-
const timeoutPromise = new Promise((resolve) => {
|
|
151
|
-
timer = setTimeout(() => {
|
|
152
|
-
resolve(`[Timeout] ${agent.name} exceeded ${Math.floor(timeoutMs / 1000)}s`);
|
|
153
|
-
}, timeoutMs);
|
|
154
|
-
});
|
|
155
|
-
try {
|
|
156
|
-
return await Promise.race([agent.send(messages), timeoutPromise]);
|
|
157
|
-
}
|
|
158
|
-
finally {
|
|
159
|
-
if (timer) {
|
|
160
|
-
clearTimeout(timer);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
timeoutFor(agentName) {
|
|
165
|
-
return this.agentTimeouts.get(agentName) ?? this.defaultTimeout;
|
|
166
|
-
}
|
|
167
|
-
buildInputForAgent(agentName, unseen) {
|
|
168
|
-
const recent = this.conversation.slice(-this.contextWindowSize);
|
|
169
|
-
const merged = [...recent, ...unseen];
|
|
170
|
-
const dedupById = new Map();
|
|
171
|
-
for (const msg of merged) {
|
|
172
|
-
dedupById.set(msg.id, msg);
|
|
173
|
-
}
|
|
174
|
-
const ordered = Array.from(dedupById.values()).sort((a, b) => a.id - b.id);
|
|
175
|
-
return ordered.filter((msg) => msg.from.toUpperCase() !== agentName.toUpperCase());
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
function createSessionId() {
|
|
179
|
-
const timestamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\..+/, "").replace("T", "-");
|
|
180
|
-
return `${timestamp}-${process.pid}`;
|
|
181
|
-
}
|
|
182
|
-
function defaultTagFor(name) {
|
|
183
|
-
const compact = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
184
|
-
return compact || "agent";
|
|
185
|
-
}
|
package/dist/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/ui/terminal.js
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
import readline from "node:readline/promises";
|
|
2
|
-
import { execFile } from "node:child_process";
|
|
3
|
-
import { stdin as input, stdout as output } from "node:process";
|
|
4
|
-
import chalk from "chalk";
|
|
5
|
-
import { initProjectFolder } from "../config/loader.js";
|
|
6
|
-
export async function runTerminal(orchestrator, options = {}) {
|
|
7
|
-
const rl = readline.createInterface({ input, output });
|
|
8
|
-
const humanName = orchestrator.getHumanName();
|
|
9
|
-
const tags = formatTagHints(orchestrator);
|
|
10
|
-
let lastTargets;
|
|
11
|
-
let knownChangedFiles = await getChangedFiles();
|
|
12
|
-
let projectFolderReady = false;
|
|
13
|
-
process.on("SIGINT", () => {
|
|
14
|
-
rl.close();
|
|
15
|
-
output.write("\n");
|
|
16
|
-
process.exit(0);
|
|
17
|
-
});
|
|
18
|
-
output.write(chalk.cyan(`llm-party Phase 1 started. Commands: /agents, /history, /save <path>, /session, /changes, /exit. Tags: ${tags}\n`));
|
|
19
|
-
output.write(chalk.gray(`Session: ${orchestrator.getSessionId()}\n`));
|
|
20
|
-
output.write(chalk.gray(`Transcript: ${orchestrator.getTranscriptPath()}\n`));
|
|
21
|
-
while (true) {
|
|
22
|
-
let line = "";
|
|
23
|
-
try {
|
|
24
|
-
line = (await rl.question(chalk.green(`${humanName} > `))).trim();
|
|
25
|
-
}
|
|
26
|
-
catch (error) {
|
|
27
|
-
const code = error.code;
|
|
28
|
-
if (code === "ERR_USE_AFTER_CLOSE" || code === "ABORT_ERR") {
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
31
|
-
throw error;
|
|
32
|
-
}
|
|
33
|
-
if (!line) {
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
if (line === "/exit") {
|
|
37
|
-
break;
|
|
38
|
-
}
|
|
39
|
-
if (line === "/history") {
|
|
40
|
-
const history = orchestrator.getHistory();
|
|
41
|
-
for (const msg of history) {
|
|
42
|
-
output.write(`${chalk.gray(msg.createdAt)} ${chalk.yellow("[" + msg.from + "]")} ${msg.text}\n`);
|
|
43
|
-
}
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
if (line === "/agents") {
|
|
47
|
-
const agents = orchestrator.listAgents();
|
|
48
|
-
for (const agent of agents) {
|
|
49
|
-
output.write(`${chalk.cyan(agent.name)} tag=@${agent.tag} provider=${agent.provider} model=${agent.model}\n`);
|
|
50
|
-
}
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
if (line === "/session") {
|
|
54
|
-
output.write(chalk.cyan(`Session: ${orchestrator.getSessionId()}\n`));
|
|
55
|
-
output.write(chalk.cyan(`Transcript: ${orchestrator.getTranscriptPath()}\n`));
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
if (line === "/changes") {
|
|
59
|
-
const changedFiles = await getChangedFiles();
|
|
60
|
-
if (changedFiles.length === 0) {
|
|
61
|
-
output.write(chalk.cyan("No modified files in git working tree.\n"));
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
output.write(chalk.cyan("Modified files:\n"));
|
|
65
|
-
for (const file of changedFiles) {
|
|
66
|
-
output.write(`- ${file}\n`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
if (line.startsWith("/save ")) {
|
|
72
|
-
const filePath = line.replace("/save ", "").trim();
|
|
73
|
-
if (!filePath) {
|
|
74
|
-
output.write(chalk.red("Usage: /save <path>\n"));
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
await orchestrator.saveHistory(filePath);
|
|
78
|
-
output.write(chalk.cyan(`Saved history to ${filePath}\n`));
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
const routing = parseRouting(line);
|
|
82
|
-
const explicitTargets = routing.targets && routing.targets.length > 0
|
|
83
|
-
? Array.from(new Set(routing.targets.flatMap((target) => orchestrator.resolveTargets(target))))
|
|
84
|
-
: undefined;
|
|
85
|
-
if (routing.targets && routing.targets.length > 0 && (!explicitTargets || explicitTargets.length === 0)) {
|
|
86
|
-
output.write(chalk.red(`No agent matched ${routing.targets.map((target) => `@${target}`).join(", ")}. Use /agents to list names/providers.\n`));
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
const targets = explicitTargets ?? lastTargets;
|
|
90
|
-
if (explicitTargets && explicitTargets.length > 0) {
|
|
91
|
-
lastTargets = explicitTargets;
|
|
92
|
-
}
|
|
93
|
-
if (!projectFolderReady) {
|
|
94
|
-
await initProjectFolder(process.cwd());
|
|
95
|
-
projectFolderReady = true;
|
|
96
|
-
}
|
|
97
|
-
const userMessage = orchestrator.addUserMessage(routing.message);
|
|
98
|
-
await orchestrator.appendTranscript(userMessage);
|
|
99
|
-
knownChangedFiles = await dispatchWithHandoffs(orchestrator, output, targets, knownChangedFiles, options.maxAutoHops ?? 6);
|
|
100
|
-
}
|
|
101
|
-
rl.close();
|
|
102
|
-
}
|
|
103
|
-
async function getChangedFiles() {
|
|
104
|
-
return new Promise((resolve) => {
|
|
105
|
-
execFile("git", ["status", "--porcelain"], { cwd: process.cwd() }, (error, stdout) => {
|
|
106
|
-
if (error) {
|
|
107
|
-
resolve([]);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
const files = stdout
|
|
111
|
-
.split("\n")
|
|
112
|
-
.filter((line) => line.length >= 4)
|
|
113
|
-
.map((line) => line.slice(3).trim());
|
|
114
|
-
resolve(Array.from(new Set(files)));
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
async function dispatchWithHandoffs(orchestrator, out, initialTargets, previousChangedFiles = [], maxHops = 6) {
|
|
119
|
-
let targets = initialTargets;
|
|
120
|
-
let hops = 0;
|
|
121
|
-
let knownChangedFiles = previousChangedFiles;
|
|
122
|
-
while (true) {
|
|
123
|
-
const targetLabel = targets && targets.length > 0 ? targets.join(",") : "all";
|
|
124
|
-
out.write(chalk.gray(`Dispatching to ${targetLabel}...\n`));
|
|
125
|
-
const batch = [];
|
|
126
|
-
await orchestrator.fanOutWithProgress(targets, (msg) => {
|
|
127
|
-
batch.push(msg);
|
|
128
|
-
out.write(chalk.magenta(`[${msg.from}]`) + ` ${msg.text}\n\n`);
|
|
129
|
-
});
|
|
130
|
-
const latestChangedFiles = await getChangedFiles();
|
|
131
|
-
const newlyChanged = diffChangedFiles(knownChangedFiles, latestChangedFiles);
|
|
132
|
-
if (newlyChanged.length > 0) {
|
|
133
|
-
out.write(chalk.yellow(`LLM modified files at ${new Date().toISOString()}:\n`));
|
|
134
|
-
for (const file of newlyChanged) {
|
|
135
|
-
out.write(chalk.yellow(`- ${file}\n`));
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
knownChangedFiles = latestChangedFiles;
|
|
139
|
-
const nextSelectors = extractNextSelectors(batch);
|
|
140
|
-
if (nextSelectors.length === 0) {
|
|
141
|
-
return knownChangedFiles;
|
|
142
|
-
}
|
|
143
|
-
if (nextSelectors.some((selector) => {
|
|
144
|
-
const normalized = selector.toLowerCase();
|
|
145
|
-
return normalized === orchestrator.getHumanTag().toLowerCase()
|
|
146
|
-
|| normalized === orchestrator.getHumanName().toLowerCase();
|
|
147
|
-
})) {
|
|
148
|
-
return knownChangedFiles;
|
|
149
|
-
}
|
|
150
|
-
const resolvedTargets = Array.from(new Set(nextSelectors.flatMap((selector) => orchestrator.resolveTargets(selector))));
|
|
151
|
-
if (resolvedTargets.length === 0) {
|
|
152
|
-
out.write(chalk.yellow(`Ignored @next target(s): ${nextSelectors.join(",")}\n`));
|
|
153
|
-
return knownChangedFiles;
|
|
154
|
-
}
|
|
155
|
-
hops += 1;
|
|
156
|
-
if (Number.isFinite(maxHops) && hops >= maxHops) {
|
|
157
|
-
out.write(chalk.yellow(`Stopped auto-handoff after ${maxHops} hops to prevent loops.\n`));
|
|
158
|
-
return knownChangedFiles;
|
|
159
|
-
}
|
|
160
|
-
out.write(chalk.gray(`Auto handoff via @next to ${resolvedTargets.join(",")}\n`));
|
|
161
|
-
targets = resolvedTargets;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
function diffChangedFiles(before, after) {
|
|
165
|
-
const beforeSet = new Set(before);
|
|
166
|
-
return after.filter((file) => !beforeSet.has(file));
|
|
167
|
-
}
|
|
168
|
-
function formatTagHints(orchestrator) {
|
|
169
|
-
const agents = orchestrator.listAgents();
|
|
170
|
-
const tags = new Set();
|
|
171
|
-
tags.add("@all");
|
|
172
|
-
tags.add("@everyone");
|
|
173
|
-
for (const agent of agents) {
|
|
174
|
-
tags.add(`@${agent.tag}`);
|
|
175
|
-
tags.add(`@${agent.provider}`);
|
|
176
|
-
}
|
|
177
|
-
return Array.from(tags).join(", ");
|
|
178
|
-
}
|
|
179
|
-
function extractNextSelectors(messages) {
|
|
180
|
-
const selectors = [];
|
|
181
|
-
for (const msg of messages) {
|
|
182
|
-
const regex = /@next\s*:\s*([A-Za-z0-9_-]+)/gi;
|
|
183
|
-
let match = null;
|
|
184
|
-
while ((match = regex.exec(msg.text)) !== null) {
|
|
185
|
-
selectors.push(match[1]);
|
|
186
|
-
}
|
|
187
|
-
const controlMatch = msg.text.match(/@control[\s\S]*?next\s*:\s*([A-Za-z0-9_-]+)[\s\S]*?@end/i);
|
|
188
|
-
if (controlMatch?.[1]) {
|
|
189
|
-
selectors.push(controlMatch[1]);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
return selectors;
|
|
193
|
-
}
|
|
194
|
-
function parseRouting(line) {
|
|
195
|
-
const normalizedStart = line.replace(/^[^A-Za-z0-9@_-]+/, "");
|
|
196
|
-
const startMatch = normalizedStart.match(/^@([A-Za-z0-9_-]+)[\.,:;!?-]*\s+([\s\S]+)$/);
|
|
197
|
-
if (startMatch) {
|
|
198
|
-
return {
|
|
199
|
-
targets: [startMatch[1].toLowerCase()],
|
|
200
|
-
message: startMatch[2].trim()
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
const mentionRegex = /(^|[^A-Za-z0-9_-])@([A-Za-z0-9_-]+)\b/g;
|
|
204
|
-
const targets = [];
|
|
205
|
-
let stripped = line;
|
|
206
|
-
let match = null;
|
|
207
|
-
while ((match = mentionRegex.exec(line)) !== null) {
|
|
208
|
-
targets.push(match[2].toLowerCase());
|
|
209
|
-
}
|
|
210
|
-
if (targets.length === 0) {
|
|
211
|
-
return { message: line };
|
|
212
|
-
}
|
|
213
|
-
stripped = stripped.replace(/(^|[^A-Za-z0-9_-])@([A-Za-z0-9_-]+)\b/g, (full, prefix) => prefix || "");
|
|
214
|
-
stripped = stripped.replace(/\s{2,}/g, " ").trim();
|
|
215
|
-
return {
|
|
216
|
-
targets,
|
|
217
|
-
message: stripped || line
|
|
218
|
-
};
|
|
219
|
-
}
|