jinzd-ai-cli 0.4.154 → 0.4.156
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/{batch-W57MV5OT.js → batch-DBOCPVH5.js} +2 -2
- package/dist/{chat-index-LUQWWLKO.js → chat-index-2I7ZHRE5.js} +2 -1
- package/dist/{chunk-SH7NTECG.js → chunk-3DCAQKVZ.js} +1 -1
- package/dist/{chunk-HVNEBTSF.js → chunk-3SQMKA4I.js} +1 -1
- package/dist/{chunk-UE26B3RO.js → chunk-3WLEDKOW.js} +1 -1
- package/dist/{chunk-2IODI5TI.js → chunk-7OCOFVQP.js} +1 -1
- package/dist/{chunk-NP7WOVIH.js → chunk-AACNCJMD.js} +1 -1
- package/dist/{chunk-ZAYDVWY4.js → chunk-GQ647SF3.js} +23 -787
- package/dist/{chunk-RXM76HB7.js → chunk-MM3F43H6.js} +3 -117
- package/dist/chunk-NZ4X6GUC.js +230 -0
- package/dist/{chunk-O6MLS5QO.js → chunk-OJL3PY36.js} +0 -226
- package/dist/{hub-OP7EWTQQ.js → chunk-Q3ZUDA6S.js} +10 -237
- package/dist/chunk-RUJQ5OUB.js +51 -0
- package/dist/chunk-SLSWPBK3.js +120 -0
- package/dist/chunk-TOTEUETI.js +768 -0
- package/dist/{chunk-OSTMMSOV.js → chunk-UAPNBQLU.js} +1 -1
- package/dist/{chunk-XWYWASPT.js → chunk-Z2UJDFJK.js} +4 -4
- package/dist/{ci-JYZGZSMP.js → ci-AWA6KC3X.js} +3 -3
- package/dist/{constants-S4Y6A25E.js → constants-QDTWBWWC.js} +1 -1
- package/dist/{doctor-cli-FMTMDO2Z.js → doctor-cli-4MTG6SFH.js} +6 -6
- package/dist/electron-server.js +740 -44
- package/dist/hub-QRDXG527.js +260 -0
- package/dist/{hub-server-OH7AYQIW.js → hub-server-GSTG5MNE.js} +4 -2
- package/dist/index.js +50 -44
- package/dist/persist-UI6WRBGB.js +12 -0
- package/dist/{run-tests-3QAZGHP2.js → run-tests-CNURD2ST.js} +2 -2
- package/dist/{run-tests-4XNY7QB4.js → run-tests-NPWSCWP5.js} +1 -1
- package/dist/{server-W4TBZN6I.js → server-HFG2SM3Y.js} +6 -5
- package/dist/{server-UL42EXOA.js → server-RRUCZMMM.js} +124 -29
- package/dist/{task-orchestrator-RLAZK5EB.js → task-orchestrator-GF6ZMNUK.js} +6 -5
- package/dist/web/client/app.js +138 -0
- package/dist/web/client/index.html +28 -0
- package/package.json +1 -1
|
@@ -4,126 +4,15 @@ import {
|
|
|
4
4
|
embed,
|
|
5
5
|
embedOne
|
|
6
6
|
} from "./chunk-KHYD3WXE.js";
|
|
7
|
+
import {
|
|
8
|
+
redactString
|
|
9
|
+
} from "./chunk-SLSWPBK3.js";
|
|
7
10
|
|
|
8
11
|
// src/memory/chat-index.ts
|
|
9
12
|
import fs from "fs";
|
|
10
13
|
import path from "path";
|
|
11
14
|
import os from "os";
|
|
12
15
|
import crypto from "crypto";
|
|
13
|
-
|
|
14
|
-
// src/security/redactor.ts
|
|
15
|
-
var DEFAULT_PATTERNS = [
|
|
16
|
-
// password: xxx / password = xxx / password="xxx"
|
|
17
|
-
// Covers YAML / JSON / shell-ish / env-file forms.
|
|
18
|
-
{ kind: "password", regex: /\b(password|passwd|pwd)\s*[:=]\s*["']?([^\s"',;{}]{4,200})["']?/gi },
|
|
19
|
-
// PGPASSWORD=xxx (explicit bash env-var form, separate rule because no quotes usually)
|
|
20
|
-
{ kind: "pgpassword-env", regex: /\b(PGPASSWORD)=([^\s"']{4,200})/g },
|
|
21
|
-
// JDBC/PG/MySQL/Mongo connection strings with inline credentials
|
|
22
|
-
// postgresql://user:pass@host/db → redact pass
|
|
23
|
-
{ kind: "db-uri-password", regex: /(\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp|mssql):\/\/[^:\s]+:)([^@\s]+)(@)/gi },
|
|
24
|
-
// Anthropic API keys
|
|
25
|
-
{ kind: "anthropic-key", regex: /(sk-ant-[a-zA-Z0-9_-]{90,})/g },
|
|
26
|
-
// L6 (v0.4.108): Zhipu / GLM API keys — `<24+ hex/base64-ish>.<32+>`
|
|
27
|
-
// Two segments separated by a dot, each safely identifiable by length
|
|
28
|
-
// and char class. Conservative on the lower bound so we don't eat
|
|
29
|
-
// version strings like `1.0.0` or filenames.
|
|
30
|
-
{ kind: "zhipu-key", regex: /\b([a-zA-Z0-9]{24,}\.[a-zA-Z0-9]{32,})\b/g },
|
|
31
|
-
// OpenAI / generic sk- keys — requires length ≥32 to avoid eating short identifiers
|
|
32
|
-
{ kind: "openai-key", regex: /(sk-(?:proj-)?[a-zA-Z0-9_-]{32,})/g },
|
|
33
|
-
// GitHub personal access tokens
|
|
34
|
-
{ kind: "github-pat", regex: /\b(ghp_[a-zA-Z0-9]{36})\b/g },
|
|
35
|
-
{ kind: "github-oauth", regex: /\b(gho_[a-zA-Z0-9]{36})\b/g },
|
|
36
|
-
{ kind: "github-install", regex: /\b(ghs_[a-zA-Z0-9]{36})\b/g },
|
|
37
|
-
// Slack tokens
|
|
38
|
-
{ kind: "slack-bot", regex: /\b(xoxb-\d+-\d+-[a-zA-Z0-9]+)\b/g },
|
|
39
|
-
{ kind: "slack-user", regex: /\b(xoxp-\d+-\d+-\d+-[a-zA-Z0-9]+)\b/g },
|
|
40
|
-
// AWS access key IDs (AKIA...) and secret access keys are context-dependent;
|
|
41
|
-
// we only catch the ID because secret key alone is indistinguishable from random base64.
|
|
42
|
-
{ kind: "aws-access-key-id", regex: /\b(AKIA[0-9A-Z]{16})\b/g },
|
|
43
|
-
// Google API keys
|
|
44
|
-
{ kind: "google-api-key", regex: /\b(AIza[0-9A-Za-z_-]{35})\b/g },
|
|
45
|
-
// Generic "api_key": "..." / "apiKey": "..." / api-key=xxx
|
|
46
|
-
{ kind: "api-key", regex: /\b(api[_-]?key)\s*[:=]\s*["']?([a-zA-Z0-9_\-.]{16,200})["']?/gi },
|
|
47
|
-
// Generic token: xxx (only when value looks token-shaped; avoids eating human prose)
|
|
48
|
-
{ kind: "token", regex: /\b(token|access[_-]?token|bearer[_-]?token)\s*[:=]\s*["']?([a-zA-Z0-9_\-.]{20,300})["']?/gi },
|
|
49
|
-
// Bearer <token> in Authorization headers
|
|
50
|
-
{ kind: "bearer", regex: /\b(Authorization:\s*Bearer\s+)([a-zA-Z0-9_\-.=]{20,500})/g },
|
|
51
|
-
// Private key PEM blocks — catch the header+footer together
|
|
52
|
-
{ kind: "private-key", regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g }
|
|
53
|
-
];
|
|
54
|
-
var MAX_CUSTOM = 32;
|
|
55
|
-
var MAX_PATTERN_LEN = 500;
|
|
56
|
-
var SUSPICIOUS_REDOS = /\([^)]*[+*][^)]*\)\s*[+*{]/;
|
|
57
|
-
function render(placeholder, kind) {
|
|
58
|
-
return placeholder.replace("{kind}", kind);
|
|
59
|
-
}
|
|
60
|
-
function redactString(input, options) {
|
|
61
|
-
if (!options.enabled || !input) return { redacted: input, hits: [] };
|
|
62
|
-
const placeholder = options.placeholder ?? "[REDACTED:{kind}]";
|
|
63
|
-
const customSrcs = (options.customRegexes ?? []).slice(0, MAX_CUSTOM);
|
|
64
|
-
const patterns = [
|
|
65
|
-
...options.patterns ?? DEFAULT_PATTERNS,
|
|
66
|
-
...customSrcs.flatMap((src, i) => {
|
|
67
|
-
if (typeof src !== "string" || src.length === 0 || src.length > MAX_PATTERN_LEN) return [];
|
|
68
|
-
try {
|
|
69
|
-
const flags = src.match(/^\/.*\/([gimsuy]*)$/)?.[1] ?? "";
|
|
70
|
-
const body = src.replace(/^\/(.*)\/[gimsuy]*$/, "$1");
|
|
71
|
-
if (SUSPICIOUS_REDOS.test(body)) return [];
|
|
72
|
-
const regex = new RegExp(body, flags.includes("g") ? flags : flags + "g");
|
|
73
|
-
return [{ kind: `custom-${i}`, regex }];
|
|
74
|
-
} catch {
|
|
75
|
-
return [];
|
|
76
|
-
}
|
|
77
|
-
})
|
|
78
|
-
];
|
|
79
|
-
let redacted = input;
|
|
80
|
-
const hits = [];
|
|
81
|
-
for (const { kind, regex } of patterns) {
|
|
82
|
-
const rx = new RegExp(regex.source, regex.flags);
|
|
83
|
-
const captureCount = new RegExp(rx.source + "|").exec("").length - 1;
|
|
84
|
-
redacted = redacted.replace(rx, (...args) => {
|
|
85
|
-
const match = args[0];
|
|
86
|
-
const g1 = captureCount >= 1 ? args[1] : void 0;
|
|
87
|
-
const g2 = captureCount >= 2 ? args[2] : void 0;
|
|
88
|
-
const offset = args[1 + captureCount];
|
|
89
|
-
if (captureCount >= 2 && typeof g2 === "string") {
|
|
90
|
-
hits.push({ kind, start: offset + (g1?.length ?? 0), length: g2.length, secret: g2 });
|
|
91
|
-
return `${g1}${render(placeholder, kind)}`;
|
|
92
|
-
}
|
|
93
|
-
hits.push({ kind, start: offset, length: match.length, secret: g1 ?? match });
|
|
94
|
-
return render(placeholder, kind);
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
return { redacted, hits };
|
|
98
|
-
}
|
|
99
|
-
function redactJson(value, options) {
|
|
100
|
-
if (!options.enabled) return { value, hits: [] };
|
|
101
|
-
const allHits = [];
|
|
102
|
-
function walk(v) {
|
|
103
|
-
if (typeof v === "string") {
|
|
104
|
-
const r = redactString(v, options);
|
|
105
|
-
allHits.push(...r.hits);
|
|
106
|
-
return r.redacted;
|
|
107
|
-
}
|
|
108
|
-
if (Array.isArray(v)) return v.map(walk);
|
|
109
|
-
if (v && typeof v === "object") {
|
|
110
|
-
const out = {};
|
|
111
|
-
for (const [k, vv] of Object.entries(v)) {
|
|
112
|
-
out[k] = walk(vv);
|
|
113
|
-
}
|
|
114
|
-
return out;
|
|
115
|
-
}
|
|
116
|
-
return v;
|
|
117
|
-
}
|
|
118
|
-
const redacted = walk(value);
|
|
119
|
-
return { value: redacted, hits: allHits };
|
|
120
|
-
}
|
|
121
|
-
function scanString(input, options) {
|
|
122
|
-
const { hits } = redactString(input, { ...options, enabled: true });
|
|
123
|
-
return hits;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// src/memory/chat-index.ts
|
|
127
16
|
var MEMORY_DIR_NAME = "memory-index";
|
|
128
17
|
var CHUNKS_FILE = "chunks.json";
|
|
129
18
|
var VECTORS_FILE = "vectors.vec";
|
|
@@ -458,9 +347,6 @@ function getChatIndexStatus() {
|
|
|
458
347
|
}
|
|
459
348
|
|
|
460
349
|
export {
|
|
461
|
-
DEFAULT_PATTERNS,
|
|
462
|
-
redactJson,
|
|
463
|
-
scanString,
|
|
464
350
|
chunkSession,
|
|
465
351
|
loadChatIndex,
|
|
466
352
|
clearChatIndex,
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/hub/convergence.ts
|
|
4
|
+
function convergenceThreshold(total) {
|
|
5
|
+
if (total <= 0) return Infinity;
|
|
6
|
+
return Math.ceil(total * 2 / 3);
|
|
7
|
+
}
|
|
8
|
+
function isConverged(convergedCount, total) {
|
|
9
|
+
if (convergedCount <= 0) return false;
|
|
10
|
+
return convergedCount >= convergenceThreshold(total);
|
|
11
|
+
}
|
|
12
|
+
var CONVERGED_MARKER = /\[CONVERGED\]/i;
|
|
13
|
+
function hasConvergedMarker(content) {
|
|
14
|
+
return CONVERGED_MARKER.test(content);
|
|
15
|
+
}
|
|
16
|
+
function stripConvergedMarker(content) {
|
|
17
|
+
return content.replace(/\s*\[CONVERGED\]\s*/gi, " ").trim();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/hub/agent.ts
|
|
21
|
+
var PASS_MARKER = "[PASS]";
|
|
22
|
+
function parseTurn(raw) {
|
|
23
|
+
const trimmed = raw.trim();
|
|
24
|
+
const upper = trimmed.toUpperCase();
|
|
25
|
+
if (upper.startsWith(PASS_MARKER) || upper === PASS_MARKER) {
|
|
26
|
+
return { content: "", passed: true, converged: false };
|
|
27
|
+
}
|
|
28
|
+
const converged = hasConvergedMarker(trimmed);
|
|
29
|
+
return { content: converged ? stripConvergedMarker(trimmed) : trimmed, passed: false, converged };
|
|
30
|
+
}
|
|
31
|
+
var HubAgent = class {
|
|
32
|
+
role;
|
|
33
|
+
providers;
|
|
34
|
+
defaultProvider;
|
|
35
|
+
defaultModel;
|
|
36
|
+
/** External context documents injected into system prompt */
|
|
37
|
+
context;
|
|
38
|
+
constructor(role, providers, defaultProvider, defaultModel, context) {
|
|
39
|
+
this.role = role;
|
|
40
|
+
this.providers = providers;
|
|
41
|
+
this.defaultProvider = defaultProvider;
|
|
42
|
+
this.defaultModel = defaultModel;
|
|
43
|
+
this.context = context;
|
|
44
|
+
}
|
|
45
|
+
get providerId() {
|
|
46
|
+
return this.role.provider ?? this.defaultProvider;
|
|
47
|
+
}
|
|
48
|
+
get modelId() {
|
|
49
|
+
return this.role.model ?? this.defaultModel;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Generate this agent's response given the full discussion history.
|
|
53
|
+
*
|
|
54
|
+
* Returns a DiscussionMessage, with `passed: true` if the agent outputs [PASS].
|
|
55
|
+
*/
|
|
56
|
+
async speak(topic, history, round, maxRounds, opts) {
|
|
57
|
+
const provider = this.providers.get(this.providerId);
|
|
58
|
+
if (!provider) {
|
|
59
|
+
throw new Error(`Provider "${this.providerId}" not available for agent "${this.role.id}"`);
|
|
60
|
+
}
|
|
61
|
+
const systemPrompt = this.buildSystemPrompt(topic, round, maxRounds, opts?.voteConverge);
|
|
62
|
+
const messages = this.buildMessages(history);
|
|
63
|
+
const response = await provider.chat({
|
|
64
|
+
messages,
|
|
65
|
+
model: this.modelId,
|
|
66
|
+
systemPrompt,
|
|
67
|
+
stream: false,
|
|
68
|
+
temperature: 0.7,
|
|
69
|
+
maxTokens: 4096
|
|
70
|
+
});
|
|
71
|
+
const { content, passed, converged } = parseTurn(response.content);
|
|
72
|
+
return {
|
|
73
|
+
speaker: this.role.id,
|
|
74
|
+
speakerName: this.role.name,
|
|
75
|
+
content,
|
|
76
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
77
|
+
passed,
|
|
78
|
+
converged
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Streaming version of speak() — yields tokens as they arrive.
|
|
83
|
+
* Falls back to non-streaming speak() if the provider doesn't support chatStream.
|
|
84
|
+
*/
|
|
85
|
+
async speakStream(topic, history, round, maxRounds, onToken, opts) {
|
|
86
|
+
const provider = this.providers.get(this.providerId);
|
|
87
|
+
if (!provider) {
|
|
88
|
+
throw new Error(`Provider "${this.providerId}" not available for agent "${this.role.id}"`);
|
|
89
|
+
}
|
|
90
|
+
if (!provider.chatStream) {
|
|
91
|
+
return this.speak(topic, history, round, maxRounds, opts);
|
|
92
|
+
}
|
|
93
|
+
const systemPrompt = this.buildSystemPrompt(topic, round, maxRounds, opts?.voteConverge);
|
|
94
|
+
const messages = this.buildMessages(history);
|
|
95
|
+
let raw = "";
|
|
96
|
+
const stream = provider.chatStream({
|
|
97
|
+
messages,
|
|
98
|
+
model: this.modelId,
|
|
99
|
+
systemPrompt,
|
|
100
|
+
stream: true,
|
|
101
|
+
temperature: 0.7,
|
|
102
|
+
maxTokens: 4096
|
|
103
|
+
});
|
|
104
|
+
for await (const chunk of stream) {
|
|
105
|
+
if (chunk.delta) {
|
|
106
|
+
raw += chunk.delta;
|
|
107
|
+
onToken?.(chunk.delta);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const { content, passed, converged } = parseTurn(raw);
|
|
111
|
+
return {
|
|
112
|
+
speaker: this.role.id,
|
|
113
|
+
speakerName: this.role.name,
|
|
114
|
+
content,
|
|
115
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
116
|
+
passed,
|
|
117
|
+
converged
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Generate a summary of the entire discussion from this agent's perspective.
|
|
122
|
+
*/
|
|
123
|
+
async summarize(topic, history) {
|
|
124
|
+
const provider = this.providers.get(this.providerId);
|
|
125
|
+
if (!provider) {
|
|
126
|
+
throw new Error(`Provider "${this.providerId}" not available`);
|
|
127
|
+
}
|
|
128
|
+
const messages = [
|
|
129
|
+
{
|
|
130
|
+
role: "user",
|
|
131
|
+
content: this.buildSummaryPrompt(topic, history),
|
|
132
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
133
|
+
}
|
|
134
|
+
];
|
|
135
|
+
const response = await provider.chat({
|
|
136
|
+
messages,
|
|
137
|
+
model: this.modelId,
|
|
138
|
+
stream: false,
|
|
139
|
+
temperature: 0.3,
|
|
140
|
+
maxTokens: 4096
|
|
141
|
+
});
|
|
142
|
+
return response.content.trim();
|
|
143
|
+
}
|
|
144
|
+
// ── Private ──────────────────────────────────────────────────────
|
|
145
|
+
buildSystemPrompt(topic, round, maxRounds, voteConverge) {
|
|
146
|
+
const contextSection = this.context ? `
|
|
147
|
+
|
|
148
|
+
## Reference Documents
|
|
149
|
+
${this.context}` : "";
|
|
150
|
+
const convergeRule = voteConverge ? `
|
|
151
|
+
- If you believe the group has reached a sufficient conclusion and further rounds would add little, append the marker [CONVERGED] at the very end of your message (you may still make your substantive point above it). When a 2/3 majority converges, the discussion ends.` : "";
|
|
152
|
+
return `# Multi-Agent Discussion \u2014 Role: ${this.role.name}
|
|
153
|
+
|
|
154
|
+
${this.role.persona}
|
|
155
|
+
|
|
156
|
+
## Discussion Rules
|
|
157
|
+
- You are participating in a multi-agent discussion about the topic below.
|
|
158
|
+
- You can see what other participants have said. Build on their ideas, challenge them, or add your own perspective.
|
|
159
|
+
- Stay in character as ${this.role.name}. Respond from your role's expertise and viewpoint.
|
|
160
|
+
- Keep responses concise and focused (2-6 paragraphs). Do not repeat what others have already said.
|
|
161
|
+
- If you have nothing meaningful to add (others have covered your points), respond with exactly: [PASS]${convergeRule}
|
|
162
|
+
- This is round ${round} of ${maxRounds}. ${round >= maxRounds - 1 ? "This is one of the final rounds \u2014 try to converge on conclusions." : ""}
|
|
163
|
+
- Use the language that the topic is written in (if the topic is in Chinese, respond in Chinese).
|
|
164
|
+
|
|
165
|
+
## Topic
|
|
166
|
+
${topic}${contextSection}`;
|
|
167
|
+
}
|
|
168
|
+
buildMessages(history) {
|
|
169
|
+
const messages = [];
|
|
170
|
+
for (const msg of history) {
|
|
171
|
+
if (msg.passed) continue;
|
|
172
|
+
if (msg.speaker === this.role.id) {
|
|
173
|
+
messages.push({
|
|
174
|
+
role: "assistant",
|
|
175
|
+
content: msg.content,
|
|
176
|
+
timestamp: msg.timestamp
|
|
177
|
+
});
|
|
178
|
+
} else {
|
|
179
|
+
const prefix = `[${msg.speakerName} (${msg.speaker})]:`;
|
|
180
|
+
messages.push({
|
|
181
|
+
role: "user",
|
|
182
|
+
content: `${prefix}
|
|
183
|
+
${msg.content}`,
|
|
184
|
+
timestamp: msg.timestamp
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (messages.length === 0) {
|
|
189
|
+
messages.push({
|
|
190
|
+
role: "user",
|
|
191
|
+
content: "Please share your initial thoughts on the topic described in the system prompt. You are the first to speak.",
|
|
192
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
193
|
+
});
|
|
194
|
+
} else {
|
|
195
|
+
messages.push({
|
|
196
|
+
role: "user",
|
|
197
|
+
content: "It is now your turn to respond. Consider what the other participants have said and share your perspective. If you have nothing to add, respond with [PASS].",
|
|
198
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return messages;
|
|
202
|
+
}
|
|
203
|
+
buildSummaryPrompt(topic, history) {
|
|
204
|
+
const transcript = history.filter((m) => !m.passed).map((m) => `**${m.speakerName}** (${m.speaker}):
|
|
205
|
+
${m.content}`).join("\n\n---\n\n");
|
|
206
|
+
return `Please synthesize the following multi-agent discussion into a clear, structured summary.
|
|
207
|
+
|
|
208
|
+
## Discussion Topic
|
|
209
|
+
${topic}
|
|
210
|
+
|
|
211
|
+
## Full Transcript
|
|
212
|
+
${transcript}
|
|
213
|
+
|
|
214
|
+
## Instructions
|
|
215
|
+
Produce a structured, decision-oriented synthesis with these exact sections:
|
|
216
|
+
|
|
217
|
+
1. **Decision / Recommendation** \u2014 the single clearest recommendation the discussion points to. If the group genuinely did not converge, say so and give the leading option plus what would settle it.
|
|
218
|
+
2. **Key Points of Agreement** \u2014 what participants agreed on.
|
|
219
|
+
3. **Points of Debate** \u2014 where they disagreed, with the competing perspectives.
|
|
220
|
+
4. **Action Items** \u2014 concrete next steps as a checklist. For each, suggest which role/expertise should own it, e.g. "- [ ] (\u67B6\u6784\u5E08) \u8BC4\u4F30\u670D\u52A1\u62C6\u5206\u8FB9\u754C".
|
|
221
|
+
5. **Risks & Open Questions** \u2014 unresolved issues and what to watch.
|
|
222
|
+
|
|
223
|
+
Keep it tight (within ~600 words). Use the same language as the discussion.`;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
export {
|
|
228
|
+
isConverged,
|
|
229
|
+
HubAgent
|
|
230
|
+
};
|
|
@@ -1,229 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/hub/convergence.ts
|
|
4
|
-
function convergenceThreshold(total) {
|
|
5
|
-
if (total <= 0) return Infinity;
|
|
6
|
-
return Math.ceil(total * 2 / 3);
|
|
7
|
-
}
|
|
8
|
-
function isConverged(convergedCount, total) {
|
|
9
|
-
if (convergedCount <= 0) return false;
|
|
10
|
-
return convergedCount >= convergenceThreshold(total);
|
|
11
|
-
}
|
|
12
|
-
var CONVERGED_MARKER = /\[CONVERGED\]/i;
|
|
13
|
-
function hasConvergedMarker(content) {
|
|
14
|
-
return CONVERGED_MARKER.test(content);
|
|
15
|
-
}
|
|
16
|
-
function stripConvergedMarker(content) {
|
|
17
|
-
return content.replace(/\s*\[CONVERGED\]\s*/gi, " ").trim();
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// src/hub/agent.ts
|
|
21
|
-
var PASS_MARKER = "[PASS]";
|
|
22
|
-
function parseTurn(raw) {
|
|
23
|
-
const trimmed = raw.trim();
|
|
24
|
-
const upper = trimmed.toUpperCase();
|
|
25
|
-
if (upper.startsWith(PASS_MARKER) || upper === PASS_MARKER) {
|
|
26
|
-
return { content: "", passed: true, converged: false };
|
|
27
|
-
}
|
|
28
|
-
const converged = hasConvergedMarker(trimmed);
|
|
29
|
-
return { content: converged ? stripConvergedMarker(trimmed) : trimmed, passed: false, converged };
|
|
30
|
-
}
|
|
31
|
-
var HubAgent = class {
|
|
32
|
-
role;
|
|
33
|
-
providers;
|
|
34
|
-
defaultProvider;
|
|
35
|
-
defaultModel;
|
|
36
|
-
/** External context documents injected into system prompt */
|
|
37
|
-
context;
|
|
38
|
-
constructor(role, providers, defaultProvider, defaultModel, context) {
|
|
39
|
-
this.role = role;
|
|
40
|
-
this.providers = providers;
|
|
41
|
-
this.defaultProvider = defaultProvider;
|
|
42
|
-
this.defaultModel = defaultModel;
|
|
43
|
-
this.context = context;
|
|
44
|
-
}
|
|
45
|
-
get providerId() {
|
|
46
|
-
return this.role.provider ?? this.defaultProvider;
|
|
47
|
-
}
|
|
48
|
-
get modelId() {
|
|
49
|
-
return this.role.model ?? this.defaultModel;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Generate this agent's response given the full discussion history.
|
|
53
|
-
*
|
|
54
|
-
* Returns a DiscussionMessage, with `passed: true` if the agent outputs [PASS].
|
|
55
|
-
*/
|
|
56
|
-
async speak(topic, history, round, maxRounds, opts) {
|
|
57
|
-
const provider = this.providers.get(this.providerId);
|
|
58
|
-
if (!provider) {
|
|
59
|
-
throw new Error(`Provider "${this.providerId}" not available for agent "${this.role.id}"`);
|
|
60
|
-
}
|
|
61
|
-
const systemPrompt = this.buildSystemPrompt(topic, round, maxRounds, opts?.voteConverge);
|
|
62
|
-
const messages = this.buildMessages(history);
|
|
63
|
-
const response = await provider.chat({
|
|
64
|
-
messages,
|
|
65
|
-
model: this.modelId,
|
|
66
|
-
systemPrompt,
|
|
67
|
-
stream: false,
|
|
68
|
-
temperature: 0.7,
|
|
69
|
-
maxTokens: 4096
|
|
70
|
-
});
|
|
71
|
-
const { content, passed, converged } = parseTurn(response.content);
|
|
72
|
-
return {
|
|
73
|
-
speaker: this.role.id,
|
|
74
|
-
speakerName: this.role.name,
|
|
75
|
-
content,
|
|
76
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
77
|
-
passed,
|
|
78
|
-
converged
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Streaming version of speak() — yields tokens as they arrive.
|
|
83
|
-
* Falls back to non-streaming speak() if the provider doesn't support chatStream.
|
|
84
|
-
*/
|
|
85
|
-
async speakStream(topic, history, round, maxRounds, onToken, opts) {
|
|
86
|
-
const provider = this.providers.get(this.providerId);
|
|
87
|
-
if (!provider) {
|
|
88
|
-
throw new Error(`Provider "${this.providerId}" not available for agent "${this.role.id}"`);
|
|
89
|
-
}
|
|
90
|
-
if (!provider.chatStream) {
|
|
91
|
-
return this.speak(topic, history, round, maxRounds, opts);
|
|
92
|
-
}
|
|
93
|
-
const systemPrompt = this.buildSystemPrompt(topic, round, maxRounds, opts?.voteConverge);
|
|
94
|
-
const messages = this.buildMessages(history);
|
|
95
|
-
let raw = "";
|
|
96
|
-
const stream = provider.chatStream({
|
|
97
|
-
messages,
|
|
98
|
-
model: this.modelId,
|
|
99
|
-
systemPrompt,
|
|
100
|
-
stream: true,
|
|
101
|
-
temperature: 0.7,
|
|
102
|
-
maxTokens: 4096
|
|
103
|
-
});
|
|
104
|
-
for await (const chunk of stream) {
|
|
105
|
-
if (chunk.delta) {
|
|
106
|
-
raw += chunk.delta;
|
|
107
|
-
onToken?.(chunk.delta);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
const { content, passed, converged } = parseTurn(raw);
|
|
111
|
-
return {
|
|
112
|
-
speaker: this.role.id,
|
|
113
|
-
speakerName: this.role.name,
|
|
114
|
-
content,
|
|
115
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
116
|
-
passed,
|
|
117
|
-
converged
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Generate a summary of the entire discussion from this agent's perspective.
|
|
122
|
-
*/
|
|
123
|
-
async summarize(topic, history) {
|
|
124
|
-
const provider = this.providers.get(this.providerId);
|
|
125
|
-
if (!provider) {
|
|
126
|
-
throw new Error(`Provider "${this.providerId}" not available`);
|
|
127
|
-
}
|
|
128
|
-
const messages = [
|
|
129
|
-
{
|
|
130
|
-
role: "user",
|
|
131
|
-
content: this.buildSummaryPrompt(topic, history),
|
|
132
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
133
|
-
}
|
|
134
|
-
];
|
|
135
|
-
const response = await provider.chat({
|
|
136
|
-
messages,
|
|
137
|
-
model: this.modelId,
|
|
138
|
-
stream: false,
|
|
139
|
-
temperature: 0.3,
|
|
140
|
-
maxTokens: 4096
|
|
141
|
-
});
|
|
142
|
-
return response.content.trim();
|
|
143
|
-
}
|
|
144
|
-
// ── Private ──────────────────────────────────────────────────────
|
|
145
|
-
buildSystemPrompt(topic, round, maxRounds, voteConverge) {
|
|
146
|
-
const contextSection = this.context ? `
|
|
147
|
-
|
|
148
|
-
## Reference Documents
|
|
149
|
-
${this.context}` : "";
|
|
150
|
-
const convergeRule = voteConverge ? `
|
|
151
|
-
- If you believe the group has reached a sufficient conclusion and further rounds would add little, append the marker [CONVERGED] at the very end of your message (you may still make your substantive point above it). When a 2/3 majority converges, the discussion ends.` : "";
|
|
152
|
-
return `# Multi-Agent Discussion \u2014 Role: ${this.role.name}
|
|
153
|
-
|
|
154
|
-
${this.role.persona}
|
|
155
|
-
|
|
156
|
-
## Discussion Rules
|
|
157
|
-
- You are participating in a multi-agent discussion about the topic below.
|
|
158
|
-
- You can see what other participants have said. Build on their ideas, challenge them, or add your own perspective.
|
|
159
|
-
- Stay in character as ${this.role.name}. Respond from your role's expertise and viewpoint.
|
|
160
|
-
- Keep responses concise and focused (2-6 paragraphs). Do not repeat what others have already said.
|
|
161
|
-
- If you have nothing meaningful to add (others have covered your points), respond with exactly: [PASS]${convergeRule}
|
|
162
|
-
- This is round ${round} of ${maxRounds}. ${round >= maxRounds - 1 ? "This is one of the final rounds \u2014 try to converge on conclusions." : ""}
|
|
163
|
-
- Use the language that the topic is written in (if the topic is in Chinese, respond in Chinese).
|
|
164
|
-
|
|
165
|
-
## Topic
|
|
166
|
-
${topic}${contextSection}`;
|
|
167
|
-
}
|
|
168
|
-
buildMessages(history) {
|
|
169
|
-
const messages = [];
|
|
170
|
-
for (const msg of history) {
|
|
171
|
-
if (msg.passed) continue;
|
|
172
|
-
if (msg.speaker === this.role.id) {
|
|
173
|
-
messages.push({
|
|
174
|
-
role: "assistant",
|
|
175
|
-
content: msg.content,
|
|
176
|
-
timestamp: msg.timestamp
|
|
177
|
-
});
|
|
178
|
-
} else {
|
|
179
|
-
const prefix = `[${msg.speakerName} (${msg.speaker})]:`;
|
|
180
|
-
messages.push({
|
|
181
|
-
role: "user",
|
|
182
|
-
content: `${prefix}
|
|
183
|
-
${msg.content}`,
|
|
184
|
-
timestamp: msg.timestamp
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
if (messages.length === 0) {
|
|
189
|
-
messages.push({
|
|
190
|
-
role: "user",
|
|
191
|
-
content: "Please share your initial thoughts on the topic described in the system prompt. You are the first to speak.",
|
|
192
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
193
|
-
});
|
|
194
|
-
} else {
|
|
195
|
-
messages.push({
|
|
196
|
-
role: "user",
|
|
197
|
-
content: "It is now your turn to respond. Consider what the other participants have said and share your perspective. If you have nothing to add, respond with [PASS].",
|
|
198
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
return messages;
|
|
202
|
-
}
|
|
203
|
-
buildSummaryPrompt(topic, history) {
|
|
204
|
-
const transcript = history.filter((m) => !m.passed).map((m) => `**${m.speakerName}** (${m.speaker}):
|
|
205
|
-
${m.content}`).join("\n\n---\n\n");
|
|
206
|
-
return `Please synthesize the following multi-agent discussion into a clear, structured summary.
|
|
207
|
-
|
|
208
|
-
## Discussion Topic
|
|
209
|
-
${topic}
|
|
210
|
-
|
|
211
|
-
## Full Transcript
|
|
212
|
-
${transcript}
|
|
213
|
-
|
|
214
|
-
## Instructions
|
|
215
|
-
Produce a structured, decision-oriented synthesis with these exact sections:
|
|
216
|
-
|
|
217
|
-
1. **Decision / Recommendation** \u2014 the single clearest recommendation the discussion points to. If the group genuinely did not converge, say so and give the leading option plus what would settle it.
|
|
218
|
-
2. **Key Points of Agreement** \u2014 what participants agreed on.
|
|
219
|
-
3. **Points of Debate** \u2014 where they disagreed, with the competing perspectives.
|
|
220
|
-
4. **Action Items** \u2014 concrete next steps as a checklist. For each, suggest which role/expertise should own it, e.g. "- [ ] (\u67B6\u6784\u5E08) \u8BC4\u4F30\u670D\u52A1\u62C6\u5206\u8FB9\u754C".
|
|
221
|
-
5. **Risks & Open Questions** \u2014 unresolved issues and what to watch.
|
|
222
|
-
|
|
223
|
-
Keep it tight (within ~600 words). Use the same language as the discussion.`;
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
|
|
227
3
|
// src/hub/renderer.ts
|
|
228
4
|
import chalk from "chalk";
|
|
229
5
|
var ROLE_COLORS = [
|
|
@@ -418,8 +194,6 @@ function stripAnsi(s) {
|
|
|
418
194
|
}
|
|
419
195
|
|
|
420
196
|
export {
|
|
421
|
-
isConverged,
|
|
422
|
-
HubAgent,
|
|
423
197
|
assignRoleColors,
|
|
424
198
|
renderHubBanner,
|
|
425
199
|
renderRoundHeader,
|