@statechange/council 0.8.0 → 0.9.1

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.
@@ -1,18 +1,18 @@
1
- import { shell as te, dialog as Ee, app as L, protocol as pe, Menu as ne, net as Se, BrowserWindow as fe, ipcMain as Ae } from "electron";
2
- import { join as d, basename as C, resolve as G, dirname as Ie } from "node:path";
3
- import { fileURLToPath as Oe, pathToFileURL as $e } from "node:url";
4
- import { existsSync as E, mkdirSync as Pe, writeFileSync as Ce, appendFileSync as Ne } from "node:fs";
5
- import { homedir as v } from "node:os";
6
- import { readFile as w, readdir as V, stat as he, mkdir as N, rm as F, writeFile as P, appendFile as xe } from "node:fs/promises";
1
+ import { shell as te, dialog as Ee, app as D, protocol as fe, Menu as ne, net as Ae, BrowserWindow as he, ipcMain as Ie } from "electron";
2
+ import { join as u, basename as P, resolve as F, dirname as $e } from "node:path";
3
+ import { fileURLToPath as Oe, pathToFileURL as Pe } from "node:url";
4
+ import { existsSync as _, mkdirSync as Ce, writeFileSync as xe, appendFileSync as Ne } from "node:fs";
5
+ import { homedir as b } from "node:os";
6
+ import { readFile as w, readdir as V, stat as ge, mkdir as C, rm as G, writeFile as O, appendFile as Re } from "node:fs/promises";
7
7
  import { execFile as R } from "node:child_process";
8
- import { promisify as U } from "node:util";
8
+ import { promisify as T } from "node:util";
9
9
  import X from "gray-matter";
10
10
  import { z as k } from "zod";
11
- import Re from "@anthropic-ai/sdk";
11
+ import Te from "@anthropic-ai/sdk";
12
12
  import Ue from "openai";
13
- import { GoogleGenerativeAI as Te } from "@google/generative-ai";
14
- import { Ollama as Le } from "ollama";
15
- import { createRequire as De } from "node:module";
13
+ import { GoogleGenerativeAI as Le } from "@google/generative-ai";
14
+ import { Ollama as De } from "ollama";
15
+ import { createRequire as je } from "node:module";
16
16
  import "dotenv/config";
17
17
  const Ke = k.object({
18
18
  name: k.string(),
@@ -23,24 +23,24 @@ const Ke = k.object({
23
23
  skills: k.array(k.string()).default([]),
24
24
  temperature: k.number().min(0).max(2).optional(),
25
25
  avatar: k.string().optional()
26
- }), je = (o, s) => [
27
- d(o, "skills", s, "SKILL.md"),
28
- d(process.cwd(), ".claude", "skills", s, "SKILL.md"),
29
- d(v(), ".agents", "skills", s, "SKILL.md"),
30
- d(v(), ".claude", "skills", s, "SKILL.md")
26
+ }), Be = (n, s) => [
27
+ u(n, "skills", s, "SKILL.md"),
28
+ u(process.cwd(), ".claude", "skills", s, "SKILL.md"),
29
+ u(b(), ".agents", "skills", s, "SKILL.md"),
30
+ u(b(), ".claude", "skills", s, "SKILL.md")
31
31
  ];
32
- async function Be(o, s) {
33
- for (const t of je(s, o))
34
- if (E(t)) {
32
+ async function Fe(n, s) {
33
+ for (const t of Be(s, n))
34
+ if (_(t)) {
35
35
  const e = await w(t, "utf-8"), { content: r } = X(e);
36
36
  return r.trim();
37
37
  }
38
38
  return null;
39
39
  }
40
- async function Ge(o, s) {
40
+ async function Ge(n, s) {
41
41
  const t = [];
42
- for (const e of o) {
43
- const r = await Be(e, s);
42
+ for (const e of n) {
43
+ const r = await Fe(e, s);
44
44
  r && t.push(`## Skill: ${e}
45
45
 
46
46
  ${r}`);
@@ -49,149 +49,149 @@ ${r}`);
49
49
 
50
50
  `);
51
51
  }
52
- function Fe(o, s) {
53
- return o ? o.startsWith("http://") || o.startsWith("https://") ? o : `council-file://${o.startsWith("/") ? o : d(s, o)}` : void 0;
52
+ function We(n, s) {
53
+ return n ? n.startsWith("http://") || n.startsWith("https://") ? n : `council-file://${n.startsWith("/") ? n : u(s, n)}` : void 0;
54
54
  }
55
- async function We(o, s) {
55
+ async function Me(n, s) {
56
56
  const t = /\{\{(.+?)\}\}/g;
57
- let e = o;
58
- for (const r of o.matchAll(t)) {
59
- const n = r[1].trim(), a = d(s, n);
60
- if (E(a)) {
57
+ let e = n;
58
+ for (const r of n.matchAll(t)) {
59
+ const o = r[1].trim(), a = u(s, o);
60
+ if (_(a)) {
61
61
  const i = await w(a, "utf-8");
62
62
  e = e.replace(r[0], i.trim());
63
63
  } else
64
- e = e.replace(r[0], `[Reference not found: ${n}]`);
64
+ e = e.replace(r[0], `[Reference not found: ${o}]`);
65
65
  }
66
66
  return e;
67
67
  }
68
- async function oe(o) {
69
- const s = G(o), t = d(s, "ABOUT.md");
70
- if (!E(t))
68
+ async function oe(n) {
69
+ const s = F(n), t = u(s, "ABOUT.md");
70
+ if (!_(t))
71
71
  throw new Error(`No ABOUT.md found in ${s}`);
72
- const e = await w(t, "utf-8"), { data: r, content: n } = X(e), a = Ke.parse(r);
73
- let i = await We(n.trim(), s);
72
+ const e = await w(t, "utf-8"), { data: r, content: o } = X(e), a = Ke.parse(r);
73
+ let i = await Me(o.trim(), s);
74
74
  if (a.skills.length > 0) {
75
- const u = await Ge(a.skills, s);
76
- u && (i += `
75
+ const l = await Ge(a.skills, s);
76
+ l && (i += `
77
77
 
78
- ` + u);
78
+ ` + l);
79
79
  }
80
80
  return {
81
- id: C(s),
81
+ id: P(s),
82
82
  frontmatter: a,
83
83
  systemPrompt: i,
84
84
  dirPath: s,
85
- avatarUrl: Fe(a.avatar, s)
85
+ avatarUrl: We(a.avatar, s)
86
86
  };
87
87
  }
88
- async function re(o, s) {
88
+ async function re(n, s) {
89
89
  const t = [], e = /* @__PURE__ */ new Set();
90
90
  if (s?.length) {
91
91
  for (const r of s)
92
- if (E(d(r, "ABOUT.md")))
92
+ if (_(u(r, "ABOUT.md")))
93
93
  try {
94
- const n = await oe(r);
95
- e.has(n.id) || (t.push(n), e.add(n.id));
94
+ const o = await oe(r);
95
+ e.has(o.id) || (t.push(o), e.add(o.id));
96
96
  } catch {
97
97
  }
98
98
  }
99
- if (E(o)) {
100
- const r = await V(o);
101
- for (const n of r) {
102
- const a = d(o, n);
103
- (await he(a)).isDirectory() && E(d(a, "ABOUT.md")) && (e.has(C(a)) || (t.push(await oe(a)), e.add(C(a))));
99
+ if (_(n)) {
100
+ const r = await V(n);
101
+ for (const o of r) {
102
+ const a = u(n, o);
103
+ (await ge(a)).isDirectory() && _(u(a, "ABOUT.md")) && (e.has(P(a)) || (t.push(await oe(a)), e.add(P(a))));
104
104
  }
105
105
  }
106
106
  if (t.length === 0)
107
107
  throw new Error(
108
- `No councilors found. Searched ${o}${s?.length ? ` and ${s.length} registered path(s)` : ""}.
108
+ `No councilors found. Searched ${n}${s?.length ? ` and ${s.length} registered path(s)` : ""}.
109
109
  Create one with: mkdir -p ~/.ai-council/councilors/my-councilor && council councilor add ~/.ai-council/councilors/my-councilor`
110
110
  );
111
111
  return t;
112
112
  }
113
- const Me = U(R), ge = d(v(), ".ai-council", "config.json"), se = d(v(), ".ai-council", "councilors");
113
+ const Ye = T(R), ye = u(b(), ".ai-council", "config.json"), se = u(b(), ".ai-council", "councilors");
114
114
  async function Q() {
115
115
  try {
116
- return JSON.parse(await w(ge, "utf-8"));
116
+ return JSON.parse(await w(ye, "utf-8"));
117
117
  } catch {
118
118
  return { backends: {} };
119
119
  }
120
120
  }
121
- async function Z(o) {
122
- const s = d(v(), ".ai-council");
123
- await N(s, { recursive: !0 }), await P(ge, JSON.stringify(o, null, 2), "utf-8");
121
+ async function Z(n) {
122
+ const s = u(b(), ".ai-council");
123
+ await C(s, { recursive: !0 }), await O(ye, JSON.stringify(n, null, 2), "utf-8");
124
124
  }
125
- function Ye(o) {
126
- return o.councilors ?? o.counsellors ?? {};
125
+ function He(n) {
126
+ return n.councilors ?? n.counsellors ?? {};
127
127
  }
128
- function ae(o) {
129
- return Object.values(Ye(o)).map((s) => s.path);
128
+ function ae(n) {
129
+ return Object.values(He(n)).map((s) => s.path);
130
130
  }
131
- async function He(o) {
132
- const s = G(o), t = d(s, "ABOUT.md");
133
- if (!E(t))
131
+ async function Je(n) {
132
+ const s = F(n), t = u(s, "ABOUT.md");
133
+ if (!_(t))
134
134
  throw new Error(`No ABOUT.md found in ${s}`);
135
- const e = C(s), r = await Q(), n = r.councilors ?? {};
136
- if (n[e])
137
- throw new Error(`Councilor "${e}" is already registered (path: ${n[e].path})`);
138
- const u = (await w(t, "utf-8")).match(/^name:\s*["']?(.+?)["']?\s*$/m)?.[1] ?? e;
139
- return n[e] = {
135
+ const e = P(s), r = await Q(), o = r.councilors ?? {};
136
+ if (o[e])
137
+ throw new Error(`Councilor "${e}" is already registered (path: ${o[e].path})`);
138
+ const l = (await w(t, "utf-8")).match(/^name:\s*["']?(.+?)["']?\s*$/m)?.[1] ?? e;
139
+ return o[e] = {
140
140
  path: s,
141
141
  source: "local",
142
142
  addedAt: (/* @__PURE__ */ new Date()).toISOString()
143
- }, r.councilors = n, await Z(r), { id: e, name: u };
143
+ }, r.councilors = o, await Z(r), { id: e, name: l };
144
144
  }
145
- async function Je(o) {
146
- await N(se, { recursive: !0 });
147
- const s = C(o, ".git").replace(/\.git$/, ""), t = d(se, s);
148
- if (E(t))
145
+ async function ze(n) {
146
+ await C(se, { recursive: !0 });
147
+ const s = P(n, ".git").replace(/\.git$/, ""), t = u(se, s);
148
+ if (_(t))
149
149
  throw new Error(`Directory already exists: ${t}. Remove it first or use a different URL.`);
150
- await Me("git", ["clone", "--depth", "1", o, t]);
151
- const e = [], r = await Q(), n = r.councilors ?? {};
152
- if (E(d(t, "ABOUT.md"))) {
150
+ await Ye("git", ["clone", "--depth", "1", n, t]);
151
+ const e = [], r = await Q(), o = r.councilors ?? {};
152
+ if (_(u(t, "ABOUT.md"))) {
153
153
  const a = s;
154
- if (n[a])
154
+ if (o[a])
155
155
  throw new Error(`Councilor "${a}" is already registered`);
156
- const c = (await w(d(t, "ABOUT.md"), "utf-8")).match(/^name:\s*["']?(.+?)["']?\s*$/m)?.[1] ?? a;
157
- n[a] = {
156
+ const c = (await w(u(t, "ABOUT.md"), "utf-8")).match(/^name:\s*["']?(.+?)["']?\s*$/m)?.[1] ?? a;
157
+ o[a] = {
158
158
  path: t,
159
159
  source: "git",
160
- url: o,
160
+ url: n,
161
161
  addedAt: (/* @__PURE__ */ new Date()).toISOString()
162
162
  }, e.push({ id: a, name: c });
163
163
  } else {
164
164
  const a = await V(t);
165
165
  for (const i of a) {
166
166
  if (i.startsWith(".")) continue;
167
- const u = d(t, i);
168
- if ((await he(u)).isDirectory() && E(d(u, "ABOUT.md"))) {
169
- const m = i;
170
- if (n[m]) continue;
171
- const p = (await w(d(u, "ABOUT.md"), "utf-8")).match(/^name:\s*["']?(.+?)["']?\s*$/m)?.[1] ?? m;
172
- n[m] = {
173
- path: u,
167
+ const l = u(t, i);
168
+ if ((await ge(l)).isDirectory() && _(u(l, "ABOUT.md"))) {
169
+ const f = i;
170
+ if (o[f]) continue;
171
+ const h = (await w(u(l, "ABOUT.md"), "utf-8")).match(/^name:\s*["']?(.+?)["']?\s*$/m)?.[1] ?? f;
172
+ o[f] = {
173
+ path: l,
174
174
  source: "git",
175
- url: o,
175
+ url: n,
176
176
  addedAt: (/* @__PURE__ */ new Date()).toISOString()
177
- }, e.push({ id: m, name: p });
177
+ }, e.push({ id: f, name: h });
178
178
  }
179
179
  }
180
180
  if (e.length === 0)
181
- throw await F(t, { recursive: !0, force: !0 }), new Error("No councilors found in cloned repository (no ABOUT.md files)");
181
+ throw await G(t, { recursive: !0, force: !0 }), new Error("No councilors found in cloned repository (no ABOUT.md files)");
182
182
  }
183
- return r.councilors = n, await Z(r), e;
183
+ return r.councilors = o, await Z(r), e;
184
184
  }
185
- async function ze(o, s = !1) {
186
- const t = await Q(), e = t.councilors ?? {}, r = e[o];
185
+ async function Ve(n, s = !1) {
186
+ const t = await Q(), e = t.councilors ?? {}, r = e[n];
187
187
  if (!r)
188
- throw new Error(`Councilor "${o}" is not registered`);
189
- s && r.source === "git" && E(r.path) && await F(r.path, { recursive: !0, force: !0 }), delete e[o], t.councilors = e, await Z(t);
188
+ throw new Error(`Councilor "${n}" is not registered`);
189
+ s && r.source === "git" && _(r.path) && await G(r.path, { recursive: !0, force: !0 }), delete e[n], t.councilors = e, await Z(t);
190
190
  }
191
- function Ve(o) {
192
- const s = new Re({
193
- apiKey: o.apiKey ?? process.env.ANTHROPIC_API_KEY,
194
- ...o.baseUrl ? { baseURL: o.baseUrl } : {}
191
+ function Xe(n) {
192
+ const s = new Te({
193
+ apiKey: n.apiKey ?? process.env.ANTHROPIC_API_KEY,
194
+ ...n.baseUrl ? { baseURL: n.baseUrl } : {}
195
195
  });
196
196
  return {
197
197
  name: "anthropic",
@@ -201,14 +201,14 @@ function Ve(o) {
201
201
  model: t.model,
202
202
  max_tokens: 4096,
203
203
  system: t.systemPrompt,
204
- messages: t.messages.map((n) => ({
205
- role: n.role,
206
- content: n.content
204
+ messages: t.messages.map((o) => ({
205
+ role: o.role,
206
+ content: o.content
207
207
  })),
208
208
  ...t.temperature !== void 0 ? { temperature: t.temperature } : {}
209
209
  });
210
210
  return {
211
- content: e.content.find((n) => n.type === "text")?.text ?? "",
211
+ content: e.content.find((o) => o.type === "text")?.text ?? "",
212
212
  tokenUsage: {
213
213
  input: e.usage.input_tokens,
214
214
  output: e.usage.output_tokens
@@ -220,14 +220,14 @@ function Ve(o) {
220
220
  model: t.model,
221
221
  max_tokens: 4096,
222
222
  system: t.systemPrompt,
223
- messages: t.messages.map((n) => ({
224
- role: n.role,
225
- content: n.content
223
+ messages: t.messages.map((o) => ({
224
+ role: o.role,
225
+ content: o.content
226
226
  })),
227
227
  ...t.temperature !== void 0 ? { temperature: t.temperature } : {}
228
228
  });
229
- for await (const n of e)
230
- n.type === "content_block_delta" && n.delta.type === "text_delta" && (yield { delta: n.delta.text });
229
+ for await (const o of e)
230
+ o.type === "content_block_delta" && o.delta.type === "text_delta" && (yield { delta: o.delta.text });
231
231
  const r = await e.finalMessage();
232
232
  yield {
233
233
  delta: "",
@@ -239,10 +239,10 @@ function Ve(o) {
239
239
  }
240
240
  };
241
241
  }
242
- function Xe(o) {
242
+ function Qe(n) {
243
243
  const s = new Ue({
244
- apiKey: o.apiKey ?? process.env.OPENAI_API_KEY,
245
- ...o.baseUrl ? { baseURL: o.baseUrl } : {}
244
+ apiKey: n.apiKey ?? process.env.OPENAI_API_KEY,
245
+ ...n.baseUrl ? { baseURL: n.baseUrl } : {}
246
246
  });
247
247
  return {
248
248
  name: "openai",
@@ -252,9 +252,9 @@ function Xe(o) {
252
252
  model: t.model,
253
253
  messages: [
254
254
  { role: "system", content: t.systemPrompt },
255
- ...t.messages.map((n) => ({
256
- role: n.role,
257
- content: n.content
255
+ ...t.messages.map((o) => ({
256
+ role: o.role,
257
+ content: o.content
258
258
  }))
259
259
  ],
260
260
  ...t.temperature !== void 0 ? { temperature: t.temperature } : {}
@@ -279,8 +279,8 @@ function Xe(o) {
279
279
  stream_options: { include_usage: !0 }
280
280
  });
281
281
  for await (const r of e) {
282
- const n = r.choices[0]?.delta?.content;
283
- n && (yield { delta: n }), r.usage && (yield {
282
+ const o = r.choices[0]?.delta?.content;
283
+ o && (yield { delta: o }), r.usage && (yield {
284
284
  delta: "",
285
285
  tokenUsage: {
286
286
  input: r.usage.prompt_tokens,
@@ -291,8 +291,8 @@ function Xe(o) {
291
291
  }
292
292
  };
293
293
  }
294
- function Qe(o) {
295
- const s = o.apiKey ?? process.env.GOOGLE_API_KEY ?? "", t = new Te(s);
294
+ function Ze(n) {
295
+ const s = n.apiKey ?? process.env.GOOGLE_API_KEY ?? "", t = new Le(s);
296
296
  return {
297
297
  name: "google",
298
298
  defaultModel: "gemini-2.0-flash",
@@ -303,10 +303,10 @@ function Qe(o) {
303
303
  generationConfig: {
304
304
  ...e.temperature !== void 0 ? { temperature: e.temperature } : {}
305
305
  }
306
- }), n = e.messages.slice(0, -1).map((m) => ({
307
- role: m.role === "assistant" ? "model" : "user",
308
- parts: [{ text: m.content }]
309
- })), a = r.startChat({ history: n }), i = e.messages[e.messages.length - 1], c = (await a.sendMessage(i?.content ?? "")).response;
306
+ }), o = e.messages.slice(0, -1).map((f) => ({
307
+ role: f.role === "assistant" ? "model" : "user",
308
+ parts: [{ text: f.content }]
309
+ })), a = r.startChat({ history: o }), i = e.messages[e.messages.length - 1], c = (await a.sendMessage(i?.content ?? "")).response;
310
310
  return {
311
311
  content: c.text(),
312
312
  tokenUsage: c.usageMetadata ? {
@@ -322,15 +322,15 @@ function Qe(o) {
322
322
  generationConfig: {
323
323
  ...e.temperature !== void 0 ? { temperature: e.temperature } : {}
324
324
  }
325
- }), n = e.messages.slice(0, -1).map((m) => ({
326
- role: m.role === "assistant" ? "model" : "user",
327
- parts: [{ text: m.content }]
328
- })), a = r.startChat({ history: n }), i = e.messages[e.messages.length - 1], u = await a.sendMessageStream(i?.content ?? "");
329
- for await (const m of u.stream) {
330
- const y = m.text();
325
+ }), o = e.messages.slice(0, -1).map((f) => ({
326
+ role: f.role === "assistant" ? "model" : "user",
327
+ parts: [{ text: f.content }]
328
+ })), a = r.startChat({ history: o }), i = e.messages[e.messages.length - 1], l = await a.sendMessageStream(i?.content ?? "");
329
+ for await (const f of l.stream) {
330
+ const y = f.text();
331
331
  y && (yield { delta: y });
332
332
  }
333
- const c = await u.response;
333
+ const c = await l.response;
334
334
  yield {
335
335
  delta: "",
336
336
  tokenUsage: c.usageMetadata ? {
@@ -341,9 +341,9 @@ function Qe(o) {
341
341
  }
342
342
  };
343
343
  }
344
- function Ze(o) {
345
- const s = new Le({
346
- host: o.baseUrl ?? "http://localhost:11434"
344
+ function qe(n) {
345
+ const s = new De({
346
+ host: n.baseUrl ?? "http://localhost:11434"
347
347
  });
348
348
  return {
349
349
  name: "ollama",
@@ -385,51 +385,51 @@ function Ze(o) {
385
385
  },
386
386
  stream: !0
387
387
  });
388
- let r, n;
388
+ let r, o;
389
389
  for await (const a of e)
390
- a.message.content && (yield { delta: a.message.content }), a.done && (r = a.prompt_eval_count, n = a.eval_count);
390
+ a.message.content && (yield { delta: a.message.content }), a.done && (r = a.prompt_eval_count, o = a.eval_count);
391
391
  yield {
392
392
  delta: "",
393
- tokenUsage: r !== void 0 ? { input: r ?? 0, output: n ?? 0 } : void 0
393
+ tokenUsage: r !== void 0 ? { input: r ?? 0, output: o ?? 0 } : void 0
394
394
  };
395
395
  }
396
396
  };
397
397
  }
398
398
  const ie = {
399
- anthropic: Ve,
400
- openai: Xe,
401
- google: Qe,
402
- ollama: Ze
399
+ anthropic: Xe,
400
+ openai: Qe,
401
+ google: Ze,
402
+ ollama: qe
403
403
  };
404
- let T = null;
405
- async function qe() {
406
- if (T) return T;
407
- const o = d(v(), ".ai-council", "config.json");
404
+ let U = null;
405
+ async function et() {
406
+ if (U) return U;
407
+ const n = u(b(), ".ai-council", "config.json");
408
408
  try {
409
- const s = await w(o, "utf-8");
410
- T = JSON.parse(s);
409
+ const s = await w(n, "utf-8");
410
+ U = JSON.parse(s);
411
411
  } catch {
412
- T = { backends: {} };
412
+ U = { backends: {} };
413
413
  }
414
- return T;
414
+ return U;
415
415
  }
416
416
  const z = /* @__PURE__ */ new Map();
417
- function et() {
418
- T = null, z.clear();
417
+ function tt() {
418
+ U = null, z.clear();
419
419
  }
420
- async function W(o) {
421
- const s = z.get(o);
420
+ async function W(n) {
421
+ const s = z.get(n);
422
422
  if (s) return s;
423
- const t = ie[o];
423
+ const t = ie[n];
424
424
  if (!t)
425
- throw new Error(`Unknown backend: "${o}". Available: ${Object.keys(ie).join(", ")}`);
426
- const r = (await qe()).backends[o] ?? {}, n = t(r);
427
- return z.set(o, n), n;
425
+ throw new Error(`Unknown backend: "${n}". Available: ${Object.keys(ie).join(", ")}`);
426
+ const r = (await et()).backends[n] ?? {}, o = t(r);
427
+ return z.set(n, o), o;
428
428
  }
429
- function tt() {
429
+ function nt() {
430
430
  return '\n## Excalidraw Element Reference\n\nOutput a JSON array of Excalidraw elements. Each element needs these fields:\n\n### Common Fields (all elements)\n- `type`: "rectangle" | "ellipse" | "diamond" | "text" | "arrow" | "line"\n- `id`: unique string (e.g. "rect1", "text1", "arrow1")\n- `x`, `y`: number (top-left origin, x increases right, y increases down)\n- `width`, `height`: number\n- `strokeColor`: hex string (e.g. "#1e1e1e")\n- `backgroundColor`: hex string or "transparent"\n- `fillStyle`: "solid" | "hachure" | "cross-hatch"\n- `strokeWidth`: 1 | 2 | 4\n- `roughness`: 0 (sharp) | 1 (sketchy)\n- `opacity`: 100\n- `angle`: 0\n- `seed`: any integer (e.g. 1)\n- `version`: 1\n- `isDeleted`: false\n- `groupIds`: []\n- `boundElements`: null or array of { id: string, type: "text" | "arrow" }\n- `link`: null\n- `locked`: false\n\n### Text Elements\nAdditional fields: `text`, `fontSize` (16-24), `fontFamily` (1=hand, 2=normal, 3=mono), `textAlign` ("left"|"center"|"right"), `verticalAlign` ("top"|"middle"), `baseline`: 0, `containerId`: null or parent shape id\n\n### Arrow/Line Elements\nAdditional fields: `points` (array of [x,y] relative to element x,y — first point always [0,0]), `startBinding` and `endBinding`: null or { elementId: string, focus: 0, gap: 5 }, `lastCommittedPoint`: null, `startArrowhead`: null, `endArrowhead`: "arrow" | null\n\n### Color Palette\n- Blue: "#1971c2", Light blue bg: "#a5d8ff"\n- Green: "#2f9e44", Light green bg: "#b2f2bb"\n- Red: "#e03131", Light red bg: "#ffc9c9"\n- Orange: "#e8590c", Light orange bg: "#ffd8a8"\n- Purple: "#7048e8", Light purple bg: "#d0bfff"\n- Yellow: "#f08c00", Light yellow bg: "#ffec99"\n- Gray: "#868e96", Light gray bg: "#dee2e6"\n- Dark: "#1e1e1e"\n\n### Layout Tips\n- Space shapes ~200px apart horizontally, ~150px vertically\n- Typical shape size: 160×80 for rectangles, 120×60 for ellipses\n- Center text inside shapes using containerId\n- Use arrows to show relationships (agreement, disagreement, influence)\n\n### Compact Example\n```json\n[\n {"type":"rectangle","id":"r1","x":50,"y":50,"width":160,"height":80,"strokeColor":"#1971c2","backgroundColor":"#a5d8ff","fillStyle":"solid","strokeWidth":2,"roughness":1,"opacity":100,"angle":0,"seed":1,"version":1,"isDeleted":false,"groupIds":[],"boundElements":[{"id":"t1","type":"text"},{"id":"a1","type":"arrow"}],"link":null,"locked":false},\n {"type":"text","id":"t1","x":60,"y":70,"width":140,"height":40,"text":"Councilor A","fontSize":16,"fontFamily":2,"textAlign":"center","verticalAlign":"middle","baseline":0,"containerId":"r1","strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":1,"roughness":0,"opacity":100,"angle":0,"seed":2,"version":1,"isDeleted":false,"groupIds":[],"boundElements":null,"link":null,"locked":false},\n {"type":"rectangle","id":"r2","x":350,"y":50,"width":160,"height":80,"strokeColor":"#2f9e44","backgroundColor":"#b2f2bb","fillStyle":"solid","strokeWidth":2,"roughness":1,"opacity":100,"angle":0,"seed":3,"version":1,"isDeleted":false,"groupIds":[],"boundElements":[{"id":"t2","type":"text"},{"id":"a1","type":"arrow"}],"link":null,"locked":false},\n {"type":"text","id":"t2","x":360,"y":70,"width":140,"height":40,"text":"Councilor B","fontSize":16,"fontFamily":2,"textAlign":"center","verticalAlign":"middle","baseline":0,"containerId":"r2","strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":1,"roughness":0,"opacity":100,"angle":0,"seed":4,"version":1,"isDeleted":false,"groupIds":[],"boundElements":null,"link":null,"locked":false},\n {"type":"arrow","id":"a1","x":210,"y":90,"width":140,"height":0,"points":[[0,0],[140,0]],"startBinding":{"elementId":"r1","focus":0,"gap":5},"endBinding":{"elementId":"r2","focus":0,"gap":5},"startArrowhead":null,"endArrowhead":"arrow","strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":2,"roughness":1,"opacity":100,"angle":0,"seed":5,"version":1,"isDeleted":false,"groupIds":[],"boundElements":null,"link":null,"locked":false,"lastCommittedPoint":null}\n]\n```\n'.trim();
431
431
  }
432
- const M = "---EXCALIDRAW---", nt = `You are the Secretary of a council discussion. Your job is to synthesize a clear, structured summary of the conversation that just took place.
432
+ const M = "---EXCALIDRAW---", ot = `You are the Secretary of a council discussion. Your job is to synthesize a clear, structured summary of the conversation that just took place.
433
433
 
434
434
  Structure your summary with these sections:
435
435
 
@@ -446,17 +446,17 @@ Where did they disagree? What are the key tensions?
446
446
  What are the most important takeaways? What would you recommend based on the full discussion?
447
447
 
448
448
  Be concise but thorough. Use markdown formatting.`;
449
- function ot(o) {
449
+ function rt(n) {
450
450
  const s = [];
451
- s.push(`Topic: ${o.topic}`), s.push(`Councilors: ${o.councilors.map((e) => e.name).join(", ")}`), s.push(`Rounds: ${o.rounds}`), s.push("");
451
+ s.push(`Topic: ${n.topic}`), s.push(`Councilors: ${n.councilors.map((e) => e.name).join(", ")}`), s.push(`Rounds: ${n.rounds}`), s.push("");
452
452
  let t = 0;
453
- for (const e of o.turns)
453
+ for (const e of n.turns)
454
454
  e.round !== t && (t = e.round, s.push(`--- Round ${t} ---`), s.push("")), s.push(`[${e.councilorName}]:`), s.push(e.content), s.push("");
455
455
  return s.join(`
456
456
  `);
457
457
  }
458
- async function rt({
459
- result: o,
458
+ async function st({
459
+ result: n,
460
460
  config: s,
461
461
  onChunk: t,
462
462
  signal: e
@@ -464,56 +464,56 @@ async function rt({
464
464
  const r = s.secretary;
465
465
  if (!r?.backend)
466
466
  throw new Error("No secretary backend configured");
467
- const n = await W(r.backend), a = r.model ?? n.defaultModel, i = r.systemPrompt || nt, u = tt(), c = `${i}
467
+ const o = await W(r.backend), a = r.model ?? o.defaultModel, i = r.systemPrompt || ot, l = nt(), c = `${i}
468
468
 
469
- ${u}
469
+ ${l}
470
470
 
471
- After your text summary, output \`${M}\` on its own line, then a JSON array of Excalidraw elements showing a visual map of where each councilor stands on the topic. Use shapes for each councilor with their name, arrows to show relationships (agreement/disagreement), and position them to visually represent the discussion dynamics.`, m = ot(o), y = {
471
+ After your text summary, output \`${M}\` on its own line, then a JSON array of Excalidraw elements showing a visual map of where each councilor stands on the topic. Use shapes for each councilor with their name, arrows to show relationships (agreement/disagreement), and position them to visually represent the discussion dynamics.`, f = rt(n), y = {
472
472
  model: a,
473
473
  systemPrompt: c,
474
474
  messages: [{ role: "user", content: `Please summarize this council discussion and create a position diagram:
475
475
 
476
- ${m}` }],
476
+ ${f}` }],
477
477
  temperature: 0.5
478
478
  };
479
- let h = "";
480
- if (n.chatStream)
481
- for await (const f of n.chatStream(y)) {
479
+ let g = "";
480
+ if (o.chatStream)
481
+ for await (const m of o.chatStream(y)) {
482
482
  if (e?.aborted) break;
483
- h += f.delta, f.delta && t && t(f.delta);
483
+ g += m.delta, m.delta && t && t(m.delta);
484
484
  }
485
485
  else
486
- h = (await n.chat(y)).content, t && t(h);
487
- const p = h.indexOf(M);
488
- if (p === -1)
489
- return { text: h.trim() };
490
- const b = h.slice(0, p).trim(), l = h.slice(p + M.length).trim();
491
- let g;
486
+ g = (await o.chat(y)).content, t && t(g);
487
+ const h = g.indexOf(M);
488
+ if (h === -1)
489
+ return { text: g.trim() };
490
+ const L = g.slice(0, h).trim(), d = g.slice(h + M.length).trim();
491
+ let p;
492
492
  try {
493
- const f = l.match(/\[[\s\S]*\]/);
494
- f && (g = JSON.parse(f[0]));
493
+ const m = d.match(/\[[\s\S]*\]/);
494
+ m && (p = JSON.parse(m[0]));
495
495
  } catch {
496
496
  }
497
- return { text: b, diagram: g };
497
+ return { text: L, diagram: p };
498
498
  }
499
- const st = "You are the Secretary of a council debate. Briefly summarize this round of discussion. Note emerging agreements, disagreements, and shifts in position. 2-3 paragraphs max. Use markdown formatting.";
500
- async function at({
501
- result: o,
499
+ const at = "You are the Secretary of a council debate. Briefly summarize this round of discussion. Note emerging agreements, disagreements, and shifts in position. 2-3 paragraphs max. Use markdown formatting.";
500
+ async function it({
501
+ result: n,
502
502
  roundNumber: s,
503
503
  config: t,
504
504
  onChunk: e,
505
505
  signal: r
506
506
  }) {
507
- const n = t.secretary;
508
- if (!n?.backend)
507
+ const o = t.secretary;
508
+ if (!o?.backend)
509
509
  throw new Error("No secretary backend configured");
510
- const a = await W(n.backend), i = n.model ?? a.defaultModel, u = o.turns.filter((h) => h.round === s), c = [];
511
- c.push(`Topic: ${o.topic}`), c.push(`Round ${s}${s === 1 ? " (Constructive)" : " (Rebuttal)"}`), c.push("");
512
- for (const h of u)
513
- c.push(`[${h.councilorName}]:`), c.push(h.content), c.push("");
514
- const m = {
510
+ const a = await W(o.backend), i = o.model ?? a.defaultModel, l = n.turns.filter((g) => g.round === s), c = [];
511
+ c.push(`Topic: ${n.topic}`), c.push(`Round ${s}${s === 1 ? " (Constructive)" : " (Rebuttal)"}`), c.push("");
512
+ for (const g of l)
513
+ c.push(`[${g.councilorName}]:`), c.push(g.content), c.push("");
514
+ const f = {
515
515
  model: i,
516
- systemPrompt: st,
516
+ systemPrompt: at,
517
517
  messages: [{ role: "user", content: `Please summarize this round:
518
518
 
519
519
  ${c.join(`
@@ -522,32 +522,32 @@ ${c.join(`
522
522
  };
523
523
  let y = "";
524
524
  if (a.chatStream)
525
- for await (const h of a.chatStream(m)) {
525
+ for await (const g of a.chatStream(f)) {
526
526
  if (r?.aborted) break;
527
- y += h.delta, h.delta && e && e(h.delta);
527
+ y += g.delta, g.delta && e && e(g.delta);
528
528
  }
529
529
  else
530
- y = (await a.chat(m)).content, e && e(y);
530
+ y = (await a.chat(f)).content, e && e(y);
531
531
  return y.trim();
532
532
  }
533
- async function it({
534
- topic: o,
533
+ async function ct({
534
+ topic: n,
535
535
  firstRoundTurns: s,
536
536
  config: t
537
537
  }) {
538
538
  const e = t.secretary;
539
539
  if (!e?.backend)
540
540
  throw new Error("No secretary backend configured");
541
- const r = await W(e.backend), n = e.model ?? r.defaultModel, a = s.map((u) => `[${u.councilorName}]: ${u.content.slice(0, 300)}`).join(`
541
+ const r = await W(e.backend), o = e.model ?? r.defaultModel, a = s.map((l) => `[${l.councilorName}]: ${l.content.slice(0, 300)}`).join(`
542
542
 
543
543
  `);
544
544
  return (await r.chat({
545
- model: n,
545
+ model: o,
546
546
  systemPrompt: "Generate a concise title (max 8 words) for this council discussion. Return only the title, no quotes or punctuation at the end.",
547
547
  messages: [
548
548
  {
549
549
  role: "user",
550
- content: `Topic: ${o}
550
+ content: `Topic: ${n}
551
551
 
552
552
  First round:
553
553
  ${a}`
@@ -556,96 +556,96 @@ ${a}`
556
556
  temperature: 0.3
557
557
  })).content.trim().replace(/^["']+|["']+$/g, "").replace(/[.!?]+$/, "").trim();
558
558
  }
559
- const ye = d(v(), ".ai-council"), ct = d(ye, "council.log");
559
+ const we = u(b(), ".ai-council"), lt = u(we, "council.log");
560
560
  let ce = !1;
561
- async function lt() {
562
- ce || (await N(ye, { recursive: !0 }), ce = !0);
561
+ async function ut() {
562
+ ce || (await C(we, { recursive: !0 }), ce = !0);
563
563
  }
564
- function ut(o, s, t, e) {
565
- let n = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${o} [${s}] ${t}`;
564
+ function dt(n, s, t, e) {
565
+ let o = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${n} [${s}] ${t}`;
566
566
  if (e !== void 0) {
567
567
  const a = e instanceof Error ? `${e.message}
568
568
  ${e.stack ?? ""}` : typeof e == "string" ? e : JSON.stringify(e, null, 2);
569
- n += `
569
+ o += `
570
570
  ${a.replace(/\n/g, `
571
571
  `)}`;
572
572
  }
573
- return n + `
573
+ return o + `
574
574
  `;
575
575
  }
576
- async function Y(o, s, t, e) {
576
+ async function Y(n, s, t, e) {
577
577
  try {
578
- await lt(), await xe(ct, ut(o, s, t, e));
578
+ await ut(), await Re(lt, dt(n, s, t, e));
579
579
  } catch {
580
580
  }
581
581
  }
582
- const _ = {
583
- info: (o, s, t) => Y("INFO", o, s, t),
584
- warn: (o, s, t) => Y("WARN", o, s, t),
585
- error: (o, s, t) => Y("ERROR", o, s, t)
582
+ const v = {
583
+ info: (n, s, t) => Y("INFO", n, s, t),
584
+ warn: (n, s, t) => Y("WARN", n, s, t),
585
+ error: (n, s, t) => Y("ERROR", n, s, t)
586
586
  };
587
- function dt(o, s, t, e, r) {
588
- const n = [{ role: "user", content: o }];
587
+ function mt(n, s, t, e, r) {
588
+ const o = [{ role: "user", content: n }];
589
589
  if (e?.length) {
590
590
  for (const a of e)
591
- a.councilorId === t ? n.push({ role: "assistant", content: a.content }) : n.push({
591
+ a.councilorId === t ? o.push({ role: "assistant", content: a.content }) : o.push({
592
592
  role: "user",
593
593
  content: `[${a.councilorName}, Round ${a.round}]: ${a.content}`
594
594
  });
595
- r && n.push({
595
+ r && o.push({
596
596
  role: "user",
597
597
  content: `[Secretary Summary]: ${r}`
598
598
  });
599
599
  }
600
600
  for (const a of s)
601
- a.councilorId === t ? n.push({ role: "assistant", content: a.content }) : n.push({
601
+ a.councilorId === t ? o.push({ role: "assistant", content: a.content }) : o.push({
602
602
  role: "user",
603
603
  content: `[${a.councilorName}, Round ${a.round}]: ${a.content}`
604
604
  });
605
- return n;
605
+ return o;
606
606
  }
607
- function mt(o, s, t, e) {
608
- const r = [{ role: "user", content: o }];
607
+ function pt(n, s, t, e) {
608
+ const r = [{ role: "user", content: n }];
609
609
  if (e === 1)
610
610
  return r;
611
- const n = s.filter((i) => i.round === 1);
612
- for (const i of n)
611
+ const o = s.filter((i) => i.round === 1);
612
+ for (const i of o)
613
613
  i.councilorId === t ? r.push({ role: "assistant", content: i.content }) : r.push({
614
614
  role: "user",
615
615
  content: `[${i.councilorName}, Constructive]: ${i.content}`
616
616
  });
617
617
  const a = e - 1;
618
618
  if (a > 1) {
619
- const i = s.filter((u) => u.round === a);
620
- for (const u of i)
621
- u.councilorId === t ? r.push({ role: "assistant", content: u.content }) : r.push({
619
+ const i = s.filter((l) => l.round === a);
620
+ for (const l of i)
621
+ l.councilorId === t ? r.push({ role: "assistant", content: l.content }) : r.push({
622
622
  role: "user",
623
- content: `[${u.councilorName}, Round ${a}]: ${u.content}`
623
+ content: `[${l.councilorName}, Round ${a}]: ${l.content}`
624
624
  });
625
625
  }
626
626
  for (const i of s)
627
627
  i.councilorId === t && i.round !== 1 && i.round !== a && i.round < e && r.push({ role: "assistant", content: i.content });
628
628
  return r;
629
629
  }
630
- function pt(o, s) {
631
- const t = [...o];
630
+ function ft(n, s) {
631
+ const t = [...n];
632
632
  let e = s | 0;
633
633
  const r = () => {
634
634
  e = e + 1831565813 | 0;
635
- let n = Math.imul(e ^ e >>> 15, 1 | e);
636
- return n = n + Math.imul(n ^ n >>> 7, 61 | n) ^ n, ((n ^ n >>> 14) >>> 0) / 4294967296;
635
+ let o = Math.imul(e ^ e >>> 15, 1 | e);
636
+ return o = o + Math.imul(o ^ o >>> 7, 61 | o) ^ o, ((o ^ o >>> 14) >>> 0) / 4294967296;
637
637
  };
638
- for (let n = t.length - 1; n > 0; n--) {
639
- const a = Math.floor(r() * (n + 1));
640
- [t[n], t[a]] = [t[a], t[n]];
638
+ for (let o = t.length - 1; o > 0; o--) {
639
+ const a = Math.floor(r() * (o + 1));
640
+ [t[o], t[a]] = [t[a], t[o]];
641
641
  }
642
642
  return t;
643
643
  }
644
- function H(o, s, t, e, r, n) {
644
+ function H(n, s, t, e, r, o) {
645
645
  return {
646
- topic: o.topic,
647
- topicSource: o.topicSource,
648
- councilors: o.councilors.map((a) => ({
646
+ topic: n.topic,
647
+ topicSource: n.topicSource,
648
+ councilors: n.councilors.map((a) => ({
649
649
  id: a.id,
650
650
  name: a.frontmatter.name,
651
651
  description: a.frontmatter.description,
@@ -653,118 +653,118 @@ function H(o, s, t, e, r, n) {
653
653
  model: a.frontmatter.model ?? "default",
654
654
  avatarUrl: a.avatarUrl
655
655
  })),
656
- rounds: o.rounds,
656
+ rounds: n.rounds,
657
657
  turns: s,
658
658
  startedAt: t,
659
659
  completedAt: (/* @__PURE__ */ new Date()).toISOString(),
660
660
  totalTokenUsage: { input: e, output: r },
661
- ...n && Object.keys(n).length > 0 ? { roundSummaries: n } : {},
662
- ...o.mode === "debate" ? { mode: "debate" } : {}
661
+ ...o && Object.keys(o).length > 0 ? { roundSummaries: o } : {},
662
+ ...n.mode === "debate" ? { mode: "debate" } : {}
663
663
  };
664
664
  }
665
- async function ft(o, s, t, e, r) {
666
- let n;
667
- typeof o == "string" ? n = {
668
- topic: o,
665
+ async function ht(n, s, t, e, r) {
666
+ let o;
667
+ typeof n == "string" ? o = {
668
+ topic: n,
669
669
  topicSource: s,
670
670
  councilors: t,
671
671
  rounds: e,
672
672
  onEvent: r
673
- } : n = o;
673
+ } : o = n;
674
674
  const a = (/* @__PURE__ */ new Date()).toISOString(), i = [];
675
- let u = 0, c = 0;
676
- const m = n.mode === "debate", y = {}, h = n.previousTurns?.length ? Math.max(...n.previousTurns.map((p) => p.round)) : 0;
677
- _.info("conversation", `Starting ${m ? "debate" : "freeform"} — ${n.councilors.length} councilors, ${n.rounds} rounds`, {
678
- councilors: n.councilors.map((p) => `${p.frontmatter.name} (${p.frontmatter.backend}/${p.frontmatter.model ?? "default"})`),
679
- topic: n.topic.slice(0, 200)
675
+ let l = 0, c = 0;
676
+ const f = o.mode === "debate", y = {}, g = o.previousTurns?.length ? Math.max(...o.previousTurns.map((h) => h.round)) : 0;
677
+ v.info("conversation", `Starting ${f ? "debate" : "freeform"} — ${o.councilors.length} councilors, ${o.rounds} rounds`, {
678
+ councilors: o.councilors.map((h) => `${h.frontmatter.name} (${h.frontmatter.backend}/${h.frontmatter.model ?? "default"})`),
679
+ topic: o.topic.slice(0, 200)
680
680
  });
681
- for (let p = 1; p <= n.rounds; p++) {
682
- const b = m && p > 1 ? pt(n.councilors, p) : n.councilors;
683
- for (const l of b) {
684
- if (n.signal?.aborted)
685
- return H(n, i, a, u, c, y);
686
- if (n.beforeTurn) {
687
- const f = await n.beforeTurn();
688
- f && (i.push(f), n.onEvent({ type: "turn_complete", turn: f }));
681
+ for (let h = 1; h <= o.rounds; h++) {
682
+ const L = f && h > 1 ? ft(o.councilors, h) : o.councilors;
683
+ for (const d of L) {
684
+ if (o.signal?.aborted)
685
+ return H(o, i, a, l, c, y);
686
+ if (o.beforeTurn) {
687
+ const m = await o.beforeTurn();
688
+ m && (i.push(m), o.onEvent({ type: "turn_complete", turn: m }));
689
689
  }
690
- const g = p + h;
691
- n.onEvent({ type: "turn_start", round: g, councilorName: l.frontmatter.name });
690
+ const p = h + g;
691
+ o.onEvent({ type: "turn_start", round: p, councilorName: d.frontmatter.name });
692
692
  try {
693
- const f = await W(l.frontmatter.backend), D = l.frontmatter.model ?? f.defaultModel, _e = m ? mt(n.topic, i, l.id, p) : dt(n.topic, i, l.id, n.previousTurns, n.previousSummary), q = {
694
- model: D,
695
- systemPrompt: l.systemPrompt,
696
- messages: _e,
697
- temperature: l.frontmatter.temperature
693
+ const m = await W(d.frontmatter.backend), x = d.frontmatter.model ?? m.defaultModel, Se = f ? pt(o.topic, i, d.id, h) : mt(o.topic, i, d.id, o.previousTurns, o.previousSummary), q = {
694
+ model: x,
695
+ systemPrompt: d.systemPrompt,
696
+ messages: Se,
697
+ temperature: d.frontmatter.temperature
698
698
  };
699
- let K, x;
700
- if (f.chatStream) {
701
- K = "";
702
- for await (const O of f.chatStream(q)) {
703
- if (n.signal?.aborted) break;
704
- K += O.delta, O.delta && n.onEvent({ type: "turn_chunk", councilorName: l.frontmatter.name, delta: O.delta }), O.tokenUsage && (x = O.tokenUsage);
699
+ let j, N;
700
+ if (m.chatStream) {
701
+ j = "";
702
+ for await (const I of m.chatStream(q)) {
703
+ if (o.signal?.aborted) break;
704
+ j += I.delta, I.delta && o.onEvent({ type: "turn_chunk", councilorName: d.frontmatter.name, delta: I.delta }), I.tokenUsage && (N = I.tokenUsage);
705
705
  }
706
706
  } else {
707
- const O = await f.chat(q);
708
- K = O.content, x = O.tokenUsage;
707
+ const I = await m.chat(q);
708
+ j = I.content, N = I.tokenUsage;
709
709
  }
710
710
  const ee = {
711
- round: g,
712
- councilorId: l.id,
713
- councilorName: l.frontmatter.name,
714
- content: K,
711
+ round: p,
712
+ councilorId: d.id,
713
+ councilorName: d.frontmatter.name,
714
+ content: j,
715
715
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
716
- model: D,
717
- backend: l.frontmatter.backend,
718
- tokenUsage: x,
719
- avatarUrl: l.avatarUrl
716
+ model: x,
717
+ backend: d.frontmatter.backend,
718
+ tokenUsage: N,
719
+ avatarUrl: d.avatarUrl
720
720
  };
721
- x && (u += x.input, c += x.output), i.push(ee), n.onEvent({ type: "turn_complete", turn: ee });
722
- } catch (f) {
723
- const D = f instanceof Error ? f.message : String(f);
724
- _.error("conversation", `Turn failed for ${l.frontmatter.name} (round ${p}, model ${l.frontmatter.model ?? "default"}, backend ${l.frontmatter.backend})`, f), n.onEvent({ type: "error", councilorName: l.frontmatter.name, error: D });
721
+ N && (l += N.input, c += N.output), i.push(ee), o.onEvent({ type: "turn_complete", turn: ee });
722
+ } catch (m) {
723
+ const x = m instanceof Error ? m.message : String(m);
724
+ v.error("conversation", `Turn failed for ${d.frontmatter.name} (round ${h}, model ${d.frontmatter.model ?? "default"}, backend ${d.frontmatter.backend})`, m), o.onEvent({ type: "error", councilorName: d.frontmatter.name, error: x });
725
725
  }
726
726
  }
727
- if (n.onEvent({ type: "round_complete", round: p }), m && n.config?.secretary?.backend && !n.signal?.aborted)
727
+ if (o.onEvent({ type: "round_complete", round: h }), f && o.config?.secretary?.backend && !o.signal?.aborted)
728
728
  try {
729
- const l = H(n, i, a, u, c, y);
730
- n.onEvent({ type: "round_summary_start", round: p });
731
- const g = await at({
732
- result: l,
733
- roundNumber: p,
734
- config: n.config,
735
- onChunk: (f) => {
736
- n.onEvent({ type: "round_summary_chunk", round: p, delta: f });
729
+ const d = H(o, i, a, l, c, y);
730
+ o.onEvent({ type: "round_summary_start", round: h });
731
+ const p = await it({
732
+ result: d,
733
+ roundNumber: h,
734
+ config: o.config,
735
+ onChunk: (m) => {
736
+ o.onEvent({ type: "round_summary_chunk", round: h, delta: m });
737
737
  },
738
- signal: n.signal
738
+ signal: o.signal
739
739
  });
740
- y[p] = g, n.onEvent({ type: "round_summary_complete", round: p, summary: g });
741
- } catch (l) {
742
- _.error("conversation", `Interim summary failed for round ${p}`, l);
740
+ y[h] = p, o.onEvent({ type: "round_summary_complete", round: h, summary: p });
741
+ } catch (d) {
742
+ v.error("conversation", `Interim summary failed for round ${h}`, d);
743
743
  }
744
744
  }
745
- return H(n, i, a, u, c, y);
745
+ return H(o, i, a, l, c, y);
746
746
  }
747
- const I = d(v(), ".ai-council", "history");
748
- function ht(o) {
749
- return o.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
747
+ const A = u(b(), ".ai-council", "history");
748
+ function gt(n) {
749
+ return n.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
750
750
  }
751
- async function gt(o) {
752
- await N(I, { recursive: !0 });
753
- const s = new Date(o.startedAt).toISOString().replace(/[:.]/g, "-").slice(0, 19), t = ht(o.topic), e = `${s}-${t}`, r = d(I, `${e}.json`);
754
- return await P(r, JSON.stringify(o, null, 2), "utf-8"), e;
751
+ async function yt(n) {
752
+ await C(A, { recursive: !0 });
753
+ const s = new Date(n.startedAt).toISOString().replace(/[:.]/g, "-").slice(0, 19), t = gt(n.topic), e = `${s}-${t}`, r = u(A, `${e}.json`);
754
+ return await O(r, JSON.stringify(n, null, 2), "utf-8"), e;
755
755
  }
756
- async function yt() {
757
- await N(I, { recursive: !0 });
758
- const o = await V(I), s = [];
759
- for (const t of o)
756
+ async function wt() {
757
+ await C(A, { recursive: !0 });
758
+ const n = await V(A), s = [];
759
+ for (const t of n)
760
760
  if (t.endsWith(".json"))
761
761
  try {
762
- const e = await w(d(I, t), "utf-8"), r = JSON.parse(e);
762
+ const e = await w(u(A, t), "utf-8"), r = JSON.parse(e);
763
763
  s.push({
764
- id: C(t, ".json"),
764
+ id: P(t, ".json"),
765
765
  topic: r.topic,
766
766
  title: r.title,
767
- councilors: r.councilors.map((n) => n.name),
767
+ councilors: r.councilors.map((o) => o.name),
768
768
  rounds: r.rounds,
769
769
  startedAt: r.startedAt,
770
770
  completedAt: r.completedAt
@@ -773,59 +773,59 @@ async function yt() {
773
773
  }
774
774
  return s.sort((t, e) => e.startedAt.localeCompare(t.startedAt));
775
775
  }
776
- async function le(o) {
777
- const s = d(I, `${o}.json`), t = await w(s, "utf-8"), e = JSON.parse(t);
776
+ async function le(n) {
777
+ const s = u(A, `${n}.json`), t = await w(s, "utf-8"), e = JSON.parse(t);
778
778
  return e.infographic && !e.infographics && (e.infographics = [e.infographic], delete e.infographic), e;
779
779
  }
780
- async function wt(o) {
781
- const s = d(I, `${o}.json`);
782
- await F(s);
780
+ async function bt(n) {
781
+ const s = u(A, `${n}.json`);
782
+ await G(s);
783
783
  }
784
- async function bt(o, s) {
785
- const t = d(I, `${o}.json`), e = await w(t, "utf-8"), r = JSON.parse(e);
786
- r.infographics || (r.infographics = []), r.infographics.push(s), await P(t, JSON.stringify(r, null, 2), "utf-8");
784
+ async function vt(n, s) {
785
+ const t = u(A, `${n}.json`), e = await w(t, "utf-8"), r = JSON.parse(e);
786
+ r.infographics || (r.infographics = []), r.infographics.push(s), await O(t, JSON.stringify(r, null, 2), "utf-8");
787
787
  }
788
- async function vt(o, s) {
789
- const t = d(I, `${o}.json`), e = await w(t, "utf-8"), r = JSON.parse(e);
790
- r.infographics && s >= 0 && s < r.infographics.length && r.infographics.splice(s, 1), await P(t, JSON.stringify(r, null, 2), "utf-8");
788
+ async function kt(n, s) {
789
+ const t = u(A, `${n}.json`), e = await w(t, "utf-8"), r = JSON.parse(e);
790
+ r.infographics && s >= 0 && s < r.infographics.length && r.infographics.splice(s, 1), await O(t, JSON.stringify(r, null, 2), "utf-8");
791
791
  }
792
- const we = De(import.meta.url);
793
- function kt(o) {
794
- const s = o.councilors.map((e) => e.name).join(", "), t = o.summary ?? o.turns.map((e) => `${e.councilorName}: ${e.content.slice(0, 200)}`).join(`
792
+ const be = je(import.meta.url);
793
+ function _t(n) {
794
+ const s = n.councilors.map((e) => e.name).join(", "), t = n.summary ?? n.turns.map((e) => `${e.councilorName}: ${e.content.slice(0, 200)}`).join(`
795
795
  `);
796
796
  return [
797
797
  "Create a professional infographic summarizing a panel discussion.",
798
- `Topic: ${o.topic.slice(0, 300)}`,
798
+ `Topic: ${n.topic.slice(0, 300)}`,
799
799
  `Key points: ${t.slice(0, 1500)}`,
800
800
  `Panelists: ${s}`,
801
801
  "Use a clean, modern design with sections for convergence points, divergence points, and key takeaways.",
802
802
  "Include relevant icons and visual hierarchy. Use a horizontal landscape layout."
803
803
  ].join(" ");
804
804
  }
805
- function _t(o) {
806
- if (o.infographic?.backend) return o.infographic.backend;
807
- const s = !!(o.backends.google?.apiKey || process.env.GOOGLE_API_KEY), t = !!(o.backends.openai?.apiKey || process.env.OPENAI_API_KEY);
805
+ function St(n) {
806
+ if (n.infographic?.backend) return n.infographic.backend;
807
+ const s = !!(n.backends.google?.apiKey || process.env.GOOGLE_API_KEY), t = !!(n.backends.openai?.apiKey || process.env.OPENAI_API_KEY);
808
808
  return s ? "google" : t ? "openai" : null;
809
809
  }
810
- async function Et(o, s) {
811
- const t = we("openai").default, n = (await new t({
810
+ async function Et(n, s) {
811
+ const t = be("openai").default, o = (await new t({
812
812
  apiKey: s.backends.openai?.apiKey || process.env.OPENAI_API_KEY,
813
813
  ...s.backends.openai?.baseUrl ? { baseURL: s.backends.openai.baseUrl } : {}
814
814
  }).images.generate({
815
815
  model: "gpt-image-1.5",
816
- prompt: o,
816
+ prompt: n,
817
817
  quality: "high",
818
818
  size: "1536x1024"
819
819
  })).data?.[0]?.b64_json;
820
- if (!n) throw new Error("No image data returned from OpenAI");
821
- return n;
820
+ if (!o) throw new Error("No image data returned from OpenAI");
821
+ return o;
822
822
  }
823
- async function St(o, s) {
824
- const { GoogleGenAI: t } = we("@google/genai"), e = s.backends.google?.apiKey || process.env.GOOGLE_API_KEY;
823
+ async function At(n, s) {
824
+ const { GoogleGenAI: t } = be("@google/genai"), e = s.backends.google?.apiKey || process.env.GOOGLE_API_KEY;
825
825
  if (!e) throw new Error("No Google API key configured");
826
826
  const a = (await new t({ apiKey: e }).models.generateContent({
827
827
  model: "gemini-3.1-flash-image-preview",
828
- contents: o,
828
+ contents: n,
829
829
  config: {
830
830
  responseModalities: ["IMAGE", "TEXT"]
831
831
  }
@@ -836,24 +836,91 @@ async function St(o, s) {
836
836
  return i.inlineData.data;
837
837
  throw new Error("No image data in Gemini response");
838
838
  }
839
- async function ue(o, s, t) {
840
- const e = t ?? _t(s);
839
+ async function ue(n, s, t) {
840
+ const e = t ?? St(s);
841
841
  if (!e) throw new Error("No image-capable backend configured (need OpenAI or Google API key)");
842
- const r = kt(o);
843
- return e === "openai" ? Et(r, s) : St(r, s);
842
+ const r = _t(n);
843
+ return e === "openai" ? Et(r, s) : At(r, s);
844
+ }
845
+ const It = /https?:\/\/[^\s)<>]+/g;
846
+ function $t(n) {
847
+ const s = n.match(It);
848
+ return s ? [...new Set(s)] : [];
849
+ }
850
+ async function Ot(n, s) {
851
+ const t = new AbortController(), e = setTimeout(() => t.abort(), 15e3);
852
+ s && s.addEventListener("abort", () => t.abort(), { once: !0 });
853
+ try {
854
+ const r = await fetch(n, {
855
+ signal: t.signal,
856
+ headers: {
857
+ "User-Agent": "Council/1.0 (AI Discussion Tool)",
858
+ Accept: "text/html, application/json, text/plain, */*"
859
+ },
860
+ redirect: "follow"
861
+ });
862
+ if (clearTimeout(e), !r.ok)
863
+ return { url: n, content: `[Failed to fetch: HTTP ${r.status}]` };
864
+ const o = r.headers.get("content-type") || "", a = await r.text();
865
+ if (o.includes("application/json"))
866
+ try {
867
+ const c = JSON.stringify(JSON.parse(a), null, 2);
868
+ return { url: n, content: c.slice(0, 8e3) };
869
+ } catch {
870
+ return { url: n, content: a.slice(0, 8e3) };
871
+ }
872
+ if (o.includes("text/plain") || o.includes("text/markdown"))
873
+ return { url: n, content: a.slice(0, 8e3) };
874
+ const { title: i, text: l } = Pt(a);
875
+ return { url: n, title: i, content: l.slice(0, 8e3) };
876
+ } catch (r) {
877
+ clearTimeout(e);
878
+ const o = r instanceof Error ? r.message : String(r);
879
+ return o.includes("abort") ? { url: n, content: "[Fetch timed out]" } : { url: n, content: `[Failed to fetch: ${o}]` };
880
+ }
881
+ }
882
+ function Pt(n) {
883
+ const s = n.match(/<title[^>]*>([\s\S]*?)<\/title>/i), t = s ? de(s[1].trim()) : "";
884
+ let e = n.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<nav[\s\S]*?<\/nav>/gi, "").replace(/<header[\s\S]*?<\/header>/gi, "").replace(/<footer[\s\S]*?<\/footer>/gi, "");
885
+ return e = e.replace(/<\/(p|div|h[1-6]|li|tr|blockquote|section|article)>/gi, `
886
+ `), e = e.replace(/<br\s*\/?>/gi, `
887
+ `), e = e.replace(/<li[^>]*>/gi, "• "), e = e.replace(/<[^>]+>/g, " "), e = de(e), e = e.split(`
888
+ `).map((r) => r.replace(/\s+/g, " ").trim()).filter(Boolean).join(`
889
+ `), { title: t, text: e };
890
+ }
891
+ function de(n) {
892
+ return n.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/g, " ").replace(/&#(\d+);/g, (s, t) => String.fromCharCode(Number(t))).replace(/&[a-zA-Z]+;/g, " ");
844
893
  }
845
- const be = {
894
+ async function Ct(n, s, t) {
895
+ const e = $t(n);
896
+ if (e.length === 0) return n;
897
+ v.info("topic-enricher", `Found ${e.length} URL(s) in topic`, { urls: e });
898
+ const r = await Promise.all(
899
+ e.map((a) => Ot(a, s))
900
+ );
901
+ let o = n;
902
+ for (const a of r) {
903
+ const i = a.title ? `Resource: ${a.title} (${a.url})` : `Resource: ${a.url}`;
904
+ o += `
905
+
906
+ ---
907
+ ${i}
908
+ ${a.content}`;
909
+ }
910
+ return v.info("topic-enricher", `Enriched topic with ${r.length} resource(s)`), o;
911
+ }
912
+ const ve = {
846
913
  anthropic: "https://api.anthropic.com",
847
914
  openai: "https://api.openai.com/v1",
848
915
  google: "https://generativelanguage.googleapis.com",
849
916
  ollama: "http://localhost:11434"
850
- }, de = [
917
+ }, me = [
851
918
  "claude-opus-4-20250514",
852
919
  "claude-sonnet-4-5-20250514",
853
920
  "claude-sonnet-4-20250514",
854
921
  "claude-haiku-4-20250414",
855
922
  "claude-3-5-haiku-20241022"
856
- ], j = [
923
+ ], K = [
857
924
  "gemini-2.5-pro",
858
925
  "gemini-2.5-flash",
859
926
  "gemini-2.0-flash",
@@ -861,12 +928,12 @@ const be = {
861
928
  "gemini-1.5-pro",
862
929
  "gemini-1.5-flash"
863
930
  ];
864
- async function At(o, s) {
931
+ async function xt(n, s) {
865
932
  try {
866
- switch (o) {
933
+ switch (n) {
867
934
  case "ollama": {
868
935
  const { Ollama: t } = await import("ollama");
869
- return { connected: !0, models: (await new t({ host: s.baseUrl || be.ollama }).list()).models.map((a) => a.name).sort() };
936
+ return { connected: !0, models: (await new t({ host: s.baseUrl || ve.ollama }).list()).models.map((a) => a.name).sort() };
870
937
  }
871
938
  case "openai": {
872
939
  const { default: t } = await import("openai");
@@ -883,46 +950,46 @@ async function At(o, s) {
883
950
  try {
884
951
  return { connected: !0, models: (await e.models.list({ limit: 100 })).data.map((a) => a.id).sort() };
885
952
  } catch {
886
- return { connected: !0, models: de };
953
+ return { connected: !0, models: me };
887
954
  }
888
955
  }
889
956
  case "google": {
890
957
  const t = s.apiKey || process.env.GOOGLE_API_KEY || "";
891
- if (!t) return { connected: !1, models: j, error: "No API key" };
958
+ if (!t) return { connected: !1, models: K, error: "No API key" };
892
959
  const e = await fetch(
893
960
  `https://generativelanguage.googleapis.com/v1beta/models?key=${t}`
894
961
  );
895
962
  if (!e.ok) {
896
963
  const i = (await e.json().catch(() => ({})))?.error?.message || `HTTP ${e.status}`;
897
- return { connected: !1, models: j, error: i };
964
+ return { connected: !1, models: K, error: i };
898
965
  }
899
- const n = ((await e.json()).models || []).filter((a) => a.name.includes("gemini") && a.supportedGenerationMethods?.includes("generateContent")).map((a) => a.name.replace("models/", "")).sort();
900
- return { connected: !0, models: n.length > 0 ? n : j };
966
+ const o = ((await e.json()).models || []).filter((a) => a.name.includes("gemini") && a.supportedGenerationMethods?.includes("generateContent")).map((a) => a.name.replace("models/", "")).sort();
967
+ return { connected: !0, models: o.length > 0 ? o : K };
901
968
  }
902
969
  default:
903
- return { connected: !1, models: [], error: `Unknown backend: ${o}` };
970
+ return { connected: !1, models: [], error: `Unknown backend: ${n}` };
904
971
  }
905
972
  } catch (t) {
906
973
  const e = t instanceof Error ? t.message : String(t);
907
- return { connected: !1, models: o === "anthropic" ? de : o === "google" ? j : [], error: e };
974
+ return { connected: !1, models: n === "anthropic" ? me : n === "google" ? K : [], error: e };
908
975
  }
909
976
  }
910
- let A = null, B = [];
911
- function It(o, s) {
912
- o.handle("app:getCouncilDir", async () => {
977
+ let S = null, B = [];
978
+ function Nt(n, s) {
979
+ n.handle("app:getCouncilDir", async () => {
913
980
  const t = process.env.COUNCIL_CWD || process.cwd();
914
- return G(t, "council");
915
- }), o.handle("councilors:list", async (t, e) => {
916
- const r = d(v(), ".ai-council", "config.json");
917
- let n = { backends: {} };
981
+ return F(t, "council");
982
+ }), n.handle("councilors:list", async (t, e) => {
983
+ const r = u(b(), ".ai-council", "config.json");
984
+ let o = { backends: {} };
918
985
  try {
919
986
  const c = await w(r, "utf-8");
920
- n = JSON.parse(c);
987
+ o = JSON.parse(c);
921
988
  } catch {
922
989
  }
923
- const a = ae(n), i = n.councilors ?? {};
990
+ const a = ae(o), i = o.councilors ?? {};
924
991
  return (await re(e, a)).map((c) => {
925
- const m = i[c.id];
992
+ const f = i[c.id];
926
993
  return {
927
994
  id: c.id,
928
995
  dirPath: c.dirPath,
@@ -933,21 +1000,21 @@ function It(o, s) {
933
1000
  temperature: c.frontmatter.temperature,
934
1001
  interests: c.frontmatter.interests,
935
1002
  avatarUrl: c.avatarUrl,
936
- source: m?.source,
937
- registryUrl: m?.url
1003
+ source: f?.source,
1004
+ registryUrl: f?.url
938
1005
  };
939
1006
  });
940
- }), o.handle("councilors:get", async (t, e) => {
941
- const r = d(e, "ABOUT.md"), n = await w(r, "utf-8"), { data: a, content: i } = X(n);
942
- return { frontmatter: a, body: i.trim(), raw: n };
943
- }), o.handle("councilors:save", async (t, e, r) => {
944
- const n = d(e, "ABOUT.md");
945
- return await P(n, r, "utf-8"), { success: !0 };
946
- }), o.handle("councilors:create", async (t, e, r, n) => {
947
- const a = d(e, r);
948
- return await N(a, { recursive: !0 }), await P(d(a, "ABOUT.md"), n, "utf-8"), { success: !0, dirPath: a };
949
- }), o.handle("councilors:delete", async (t, e) => (await F(e, { recursive: !0, force: !0 }), { success: !0 })), o.handle("config:get", async () => {
950
- const t = d(v(), ".ai-council", "config.json");
1007
+ }), n.handle("councilors:get", async (t, e) => {
1008
+ const r = u(e, "ABOUT.md"), o = await w(r, "utf-8"), { data: a, content: i } = X(o);
1009
+ return { frontmatter: a, body: i.trim(), raw: o };
1010
+ }), n.handle("councilors:save", async (t, e, r) => {
1011
+ const o = u(e, "ABOUT.md");
1012
+ return await O(o, r, "utf-8"), { success: !0 };
1013
+ }), n.handle("councilors:create", async (t, e, r, o) => {
1014
+ const a = u(e, r);
1015
+ return await C(a, { recursive: !0 }), await O(u(a, "ABOUT.md"), o, "utf-8"), { success: !0, dirPath: a };
1016
+ }), n.handle("councilors:delete", async (t, e) => (await G(e, { recursive: !0, force: !0 }), { success: !0 })), n.handle("config:get", async () => {
1017
+ const t = u(b(), ".ai-council", "config.json");
951
1018
  let e = { backends: {} };
952
1019
  try {
953
1020
  const i = await w(t, "utf-8");
@@ -958,35 +1025,35 @@ function It(o, s) {
958
1025
  ANTHROPIC_API_KEY: !!process.env.ANTHROPIC_API_KEY,
959
1026
  OPENAI_API_KEY: !!process.env.OPENAI_API_KEY,
960
1027
  GOOGLE_API_KEY: !!process.env.GOOGLE_API_KEY
961
- }, n = (i) => i ? "..." + i.slice(-4) : void 0, a = {
962
- ANTHROPIC_API_KEY: n(process.env.ANTHROPIC_API_KEY),
963
- OPENAI_API_KEY: n(process.env.OPENAI_API_KEY),
964
- GOOGLE_API_KEY: n(process.env.GOOGLE_API_KEY)
1028
+ }, o = (i) => i ? "..." + i.slice(-4) : void 0, a = {
1029
+ ANTHROPIC_API_KEY: o(process.env.ANTHROPIC_API_KEY),
1030
+ OPENAI_API_KEY: o(process.env.OPENAI_API_KEY),
1031
+ GOOGLE_API_KEY: o(process.env.GOOGLE_API_KEY)
965
1032
  };
966
- return { config: e, envStatus: r, envKeySuffix: a, defaultUrls: be };
967
- }), o.handle("backend:probe", async (t, e, r) => At(e, r)), o.handle("config:save", async (t, e) => {
968
- const r = d(v(), ".ai-council");
969
- return await N(r, { recursive: !0 }), await P(d(r, "config.json"), JSON.stringify(e, null, 2), "utf-8"), et(), { success: !0 };
970
- }), o.handle("discussion:start", async (t, e) => {
1033
+ return { config: e, envStatus: r, envKeySuffix: a, defaultUrls: ve };
1034
+ }), n.handle("backend:probe", async (t, e, r) => xt(e, r)), n.handle("config:save", async (t, e) => {
1035
+ const r = u(b(), ".ai-council");
1036
+ return await C(r, { recursive: !0 }), await O(u(r, "config.json"), JSON.stringify(e, null, 2), "utf-8"), tt(), { success: !0 };
1037
+ }), n.handle("discussion:start", async (t, e) => {
971
1038
  const r = s();
972
1039
  if (!r) return { error: "No window" };
973
- const n = (a) => {
1040
+ const o = (a) => {
974
1041
  r.isDestroyed() || r.webContents.send("discussion:event", a);
975
1042
  };
976
1043
  try {
977
- A && A.abort(), A = new AbortController(), B = [], _.info("ipc:discussion", "Starting discussion", { councilDir: e.councilDir, councilorIds: e.councilorIds, rounds: e.rounds, mode: e.mode });
978
- const a = d(v(), ".ai-council", "config.json");
1044
+ S && S.abort(), S = new AbortController(), B = [], v.info("ipc:discussion", "Starting discussion", { councilDir: e.councilDir, councilorIds: e.councilorIds, rounds: e.rounds, mode: e.mode });
1045
+ const a = u(b(), ".ai-council", "config.json");
979
1046
  let i = { backends: {} };
980
1047
  try {
981
- const l = await w(a, "utf-8");
982
- i = JSON.parse(l);
1048
+ const p = await w(a, "utf-8");
1049
+ i = JSON.parse(p);
983
1050
  } catch {
984
1051
  }
985
- const u = ae(i);
986
- _.info("ipc:discussion", `Loading councilors from ${e.councilDir} + ${u.length} registered paths`);
987
- const c = await re(e.councilDir, u), m = e.councilorIds?.length ? c.filter((l) => e.councilorIds.includes(l.id)) : c;
988
- if (_.info("ipc:discussion", `Resolved ${m.length} councilors: ${m.map((l) => l.id).join(", ")}`), m.length === 0) {
989
- n({ type: "error", councilorName: "", error: "No councilors found" });
1052
+ const l = ae(i);
1053
+ v.info("ipc:discussion", `Loading councilors from ${e.councilDir} + ${l.length} registered paths`);
1054
+ const c = await re(e.councilDir, l), f = e.councilorIds?.length ? c.filter((p) => e.councilorIds.includes(p.id)) : c;
1055
+ if (v.info("ipc:discussion", `Resolved ${f.length} councilors: ${f.map((p) => p.id).join(", ")}`), f.length === 0) {
1056
+ o({ type: "error", councilorName: "", error: "No councilors found" });
990
1057
  return;
991
1058
  }
992
1059
  const y = async () => B.length === 0 ? null : {
@@ -997,88 +1064,88 @@ function It(o, s) {
997
1064
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
998
1065
  model: "human",
999
1066
  backend: "human"
1000
- }, h = !!e.previousTurns?.length, p = {
1001
- topic: e.topic,
1067
+ }, g = await Ct(e.topic, S.signal), h = !!e.previousTurns?.length, L = {
1068
+ topic: g,
1002
1069
  topicSource: e.topicSource,
1003
- councilors: m,
1070
+ councilors: f,
1004
1071
  rounds: e.rounds,
1005
- onEvent: n,
1072
+ onEvent: o,
1006
1073
  beforeTurn: y,
1007
- signal: A.signal,
1074
+ signal: S.signal,
1008
1075
  mode: h ? "freeform" : e.mode,
1009
1076
  config: i,
1010
1077
  previousTurns: e.previousTurns,
1011
1078
  previousSummary: e.previousSummary
1012
- }, b = await ft(p);
1079
+ }, d = await ht(L);
1013
1080
  if (i.secretary?.backend)
1014
1081
  try {
1015
- n({ type: "summary_start" });
1016
- const l = await rt({
1017
- result: b,
1082
+ o({ type: "summary_start" });
1083
+ const p = await st({
1084
+ result: d,
1018
1085
  config: i,
1019
- onChunk: (g) => {
1020
- n({ type: "summary_chunk", delta: g });
1086
+ onChunk: (m) => {
1087
+ o({ type: "summary_chunk", delta: m });
1021
1088
  },
1022
- signal: A?.signal
1089
+ signal: S?.signal
1023
1090
  });
1024
- b.summary = l.text, l.diagram && (b.diagram = l.diagram), n({ type: "summary_complete", summary: l.text, diagram: l.diagram });
1025
- } catch (l) {
1026
- _.error("ipc:discussion", "Secretary summary failed", l), n({ type: "error", councilorName: "Secretary", error: l instanceof Error ? l.message : String(l) });
1091
+ d.summary = p.text, p.diagram && (d.diagram = p.diagram), o({ type: "summary_complete", summary: p.text, diagram: p.diagram });
1092
+ } catch (p) {
1093
+ v.error("ipc:discussion", "Secretary summary failed", p), o({ type: "error", councilorName: "Secretary", error: p instanceof Error ? p.message : String(p) });
1027
1094
  }
1028
1095
  if (i.secretary?.backend)
1029
1096
  try {
1030
- const l = b.turns.filter((f) => f.round === 1), g = await it({
1031
- topic: b.topic,
1032
- firstRoundTurns: l,
1097
+ const p = d.turns.filter((x) => x.round === 1), m = await ct({
1098
+ topic: d.topic,
1099
+ firstRoundTurns: p,
1033
1100
  config: i
1034
1101
  });
1035
- b.title = g, n({ type: "title_generated", title: g });
1036
- } catch (l) {
1037
- _.error("ipc:discussion", "Title generation failed", l);
1102
+ d.title = m, o({ type: "title_generated", title: m });
1103
+ } catch (p) {
1104
+ v.error("ipc:discussion", "Title generation failed", p);
1038
1105
  }
1039
1106
  if (e.infographicBackends?.length)
1040
- for (const l of e.infographicBackends)
1107
+ for (const p of e.infographicBackends)
1041
1108
  try {
1042
- n({ type: "infographic_start" });
1043
- const g = await ue(b, i, l);
1044
- b.infographics || (b.infographics = []), b.infographics.push(g), n({ type: "infographic_complete", infographic: g });
1045
- } catch (g) {
1046
- _.error("ipc:discussion", `Infographic generation failed (${l})`, g), n({ type: "infographic_error", error: g instanceof Error ? g.message : String(g) });
1109
+ o({ type: "infographic_start" });
1110
+ const m = await ue(d, i, p);
1111
+ d.infographics || (d.infographics = []), d.infographics.push(m), o({ type: "infographic_complete", infographic: m });
1112
+ } catch (m) {
1113
+ v.error("ipc:discussion", `Infographic generation failed (${p})`, m), o({ type: "infographic_error", error: m instanceof Error ? m.message : String(m) });
1047
1114
  }
1048
- e.continuedFrom && (b.continuedFrom = e.continuedFrom), n({ type: "complete", result: b });
1115
+ e.continuedFrom && (d.continuedFrom = e.continuedFrom), o({ type: "complete", result: d });
1049
1116
  try {
1050
- await gt(b);
1051
- } catch (l) {
1052
- _.error("ipc:discussion", "Failed to save to history", l);
1117
+ await yt(d);
1118
+ } catch (p) {
1119
+ v.error("ipc:discussion", "Failed to save to history", p);
1053
1120
  }
1054
1121
  } catch (a) {
1055
- _.error("ipc:discussion", "Discussion failed", a), n({ type: "error", councilorName: "", error: a instanceof Error ? a.message : String(a) });
1122
+ v.error("ipc:discussion", "Discussion failed", a), o({ type: "error", councilorName: "", error: a instanceof Error ? a.message : String(a) });
1056
1123
  } finally {
1057
- A = null;
1124
+ S = null;
1058
1125
  }
1059
- }), o.handle("discussion:stop", async () => (A && (A.abort(), A = null), { success: !0 })), o.handle("discussion:inject", async (t, e) => (B.push(e), { success: !0 })), o.handle("registry:add-local", async (t, e) => He(e)), o.handle("registry:add-remote", async (t, e) => Je(e)), o.handle("registry:remove", async (t, e, r) => (await ze(e, r), { success: !0 })), o.handle("shell:open-in-finder", async (t, e) => {
1060
- te.showItemInFolder(d(e, "ABOUT.md"));
1061
- }), o.handle("shell:open-in-terminal", async (t, e) => {
1062
- await U(R)("open", ["-a", "Terminal", e]);
1063
- }), o.handle("shell:open-in-editor", async (t, e) => {
1064
- const r = U(R);
1126
+ }), n.handle("discussion:stop", async () => (S && (S.abort(), S = null), { success: !0 })), n.handle("discussion:inject", async (t, e) => (B.push(e), { success: !0 })), n.handle("registry:add-local", async (t, e) => Je(e)), n.handle("registry:add-remote", async (t, e) => ze(e)), n.handle("registry:remove", async (t, e, r) => (await Ve(e, r), { success: !0 })), n.handle("shell:open-in-finder", async (t, e) => {
1127
+ te.showItemInFolder(u(e, "ABOUT.md"));
1128
+ }), n.handle("shell:open-in-terminal", async (t, e) => {
1129
+ await T(R)("open", ["-a", "Terminal", e]);
1130
+ }), n.handle("shell:open-in-editor", async (t, e) => {
1131
+ const r = T(R);
1065
1132
  try {
1066
1133
  await r("code", [e]);
1067
1134
  } catch {
1068
1135
  te.openPath(e);
1069
1136
  }
1070
- }), o.handle("history:list", async () => yt()), o.handle("history:get", async (t, e) => le(e)), o.handle("history:delete", async (t, e) => (await wt(e), { success: !0 })), o.handle("infographic:generate", async (t, e, r) => {
1071
- const n = d(v(), ".ai-council", "config.json");
1137
+ }), n.handle("history:list", async () => wt()), n.handle("history:get", async (t, e) => le(e)), n.handle("history:delete", async (t, e) => (await bt(e), { success: !0 })), n.handle("infographic:generate", async (t, e, r) => {
1138
+ const o = u(b(), ".ai-council", "config.json");
1072
1139
  let a = { backends: {} };
1073
1140
  try {
1074
- const c = await w(n, "utf-8");
1141
+ const c = await w(o, "utf-8");
1075
1142
  a = JSON.parse(c);
1076
1143
  } catch {
1077
1144
  }
1078
- const i = await le(e), u = await ue(i, a, r);
1079
- return await bt(e, u), { infographic: u };
1080
- }), o.handle("infographic:delete", async (t, e, r) => (await vt(e, r), { success: !0 })), o.handle("file:read-as-text", async (t, e) => {
1081
- const r = C(e), n = r.includes(".") ? "." + r.split(".").pop().toLowerCase() : "", a = /* @__PURE__ */ new Set([
1145
+ const i = await le(e), l = await ue(i, a, r);
1146
+ return await vt(e, l), { infographic: l };
1147
+ }), n.handle("infographic:delete", async (t, e, r) => (await kt(e, r), { success: !0 })), n.handle("file:read-as-text", async (t, e) => {
1148
+ const r = P(e), o = r.includes(".") ? "." + r.split(".").pop().toLowerCase() : "", a = /* @__PURE__ */ new Set([
1082
1149
  ".txt",
1083
1150
  ".md",
1084
1151
  ".csv",
@@ -1126,41 +1193,41 @@ function It(o, s) {
1126
1193
  ".epub",
1127
1194
  ".rtf"
1128
1195
  ]);
1129
- if (a.has(n))
1196
+ if (a.has(o))
1130
1197
  try {
1131
- const u = await w(e, "utf-8");
1132
- return { name: r, content: u };
1133
- } catch (u) {
1134
- return { name: r, content: `[Error reading file: ${u instanceof Error ? u.message : String(u)}]` };
1198
+ const l = await w(e, "utf-8");
1199
+ return { name: r, content: l };
1200
+ } catch (l) {
1201
+ return { name: r, content: `[Error reading file: ${l instanceof Error ? l.message : String(l)}]` };
1135
1202
  }
1136
- if (i.has(n)) {
1137
- const u = U(R);
1203
+ if (i.has(o)) {
1204
+ const l = T(R);
1138
1205
  try {
1139
- const { stdout: c } = await u("markitdown", [e], {
1206
+ const { stdout: c } = await l("markitdown", [e], {
1140
1207
  timeout: 3e4,
1141
1208
  maxBuffer: 10485760
1142
1209
  // 10 MB
1143
1210
  });
1144
1211
  return { name: r, content: c };
1145
1212
  } catch (c) {
1146
- const m = c instanceof Error ? c.message : String(c);
1147
- return m.includes("ENOENT") ? {
1213
+ const f = c instanceof Error ? c.message : String(c);
1214
+ return f.includes("ENOENT") ? {
1148
1215
  name: r,
1149
- content: `[Cannot convert ${n} file: markitdown is not installed. Run: pip install 'markitdown[all]']`
1150
- } : { name: r, content: `[Error converting file: ${m}]` };
1216
+ content: `[Cannot convert ${o} file: markitdown is not installed. Run: pip install 'markitdown[all]']`
1217
+ } : { name: r, content: `[Error converting file: ${f}]` };
1151
1218
  }
1152
1219
  }
1153
1220
  return { name: r, content: `[Unsupported file type: ${r}]` };
1154
- }), o.handle("markitdown:check", async () => {
1155
- const t = U(R);
1221
+ }), n.handle("markitdown:check", async () => {
1222
+ const t = T(R);
1156
1223
  try {
1157
1224
  const { stdout: e } = await t("markitdown", ["--version"], { timeout: 5e3 });
1158
1225
  return { installed: !0, version: e.trim() };
1159
1226
  } catch {
1160
1227
  return { installed: !1 };
1161
1228
  }
1162
- }), o.handle("markitdown:install", async () => {
1163
- const t = U(R);
1229
+ }), n.handle("markitdown:install", async () => {
1230
+ const t = T(R);
1164
1231
  try {
1165
1232
  return await t("pip", ["install", "markitdown[all]"], {
1166
1233
  timeout: 12e4,
@@ -1169,7 +1236,7 @@ function It(o, s) {
1169
1236
  } catch (e) {
1170
1237
  return { success: !1, error: e instanceof Error ? e.message : String(e) };
1171
1238
  }
1172
- }), o.handle("dialog:selectDirectory", async () => {
1239
+ }), n.handle("dialog:selectDirectory", async () => {
1173
1240
  const t = s();
1174
1241
  if (!t) return null;
1175
1242
  const e = await Ee.showOpenDialog(t, {
@@ -1178,68 +1245,68 @@ function It(o, s) {
1178
1245
  return e.canceled ? null : e.filePaths[0];
1179
1246
  });
1180
1247
  }
1181
- const Ot = Oe(import.meta.url), J = Ie(Ot), ve = d(v(), ".ai-council");
1182
- Pe(ve, { recursive: !0 });
1183
- const ke = d(ve, "electron-debug.log");
1184
- function $(o, ...s) {
1185
- const t = `[${(/* @__PURE__ */ new Date()).toISOString()}] [${o}] ${s.map((e) => typeof e == "string" ? e : JSON.stringify(e)).join(" ")}
1248
+ const Rt = Oe(import.meta.url), J = $e(Rt), ke = u(b(), ".ai-council");
1249
+ Ce(ke, { recursive: !0 });
1250
+ const _e = u(ke, "electron-debug.log");
1251
+ function $(n, ...s) {
1252
+ const t = `[${(/* @__PURE__ */ new Date()).toISOString()}] [${n}] ${s.map((e) => typeof e == "string" ? e : JSON.stringify(e)).join(" ")}
1186
1253
  `;
1187
- Ne(ke, t);
1254
+ Ne(_e, t);
1188
1255
  }
1189
- Ce(ke, `=== State Change Council Electron — started ${(/* @__PURE__ */ new Date()).toISOString()} ===
1256
+ xe(_e, `=== State Change Council Electron — started ${(/* @__PURE__ */ new Date()).toISOString()} ===
1190
1257
  `);
1191
- L.name = "State Change Council";
1192
- let S = null;
1193
- function me() {
1194
- $("main", "Creating BrowserWindow"), S = new fe({
1258
+ D.name = "State Change Council";
1259
+ let E = null;
1260
+ function pe() {
1261
+ $("main", "Creating BrowserWindow"), E = new he({
1195
1262
  width: 1200,
1196
1263
  height: 800,
1197
1264
  minWidth: 800,
1198
1265
  minHeight: 600,
1199
1266
  title: "State Change Council",
1200
- icon: G(J, "..", "assets", "icon.png"),
1267
+ icon: F(J, "..", "assets", "icon.png"),
1201
1268
  webPreferences: {
1202
1269
  contextIsolation: !0,
1203
1270
  nodeIntegration: !1,
1204
- preload: d(J, "preload.mjs")
1271
+ preload: u(J, "preload.mjs")
1205
1272
  }
1206
- }), S.webContents.on("console-message", (s, t, e, r, n) => {
1273
+ }), E.webContents.on("console-message", (s, t, e, r, o) => {
1207
1274
  const a = ["DEBUG", "INFO", "WARN", "ERROR"][t] || "LOG";
1208
- $(`renderer:${a}`, `${e} (${n}:${r})`);
1209
- }), S.webContents.on("render-process-gone", (s, t) => {
1275
+ $(`renderer:${a}`, `${e} (${o}:${r})`);
1276
+ }), E.webContents.on("render-process-gone", (s, t) => {
1210
1277
  $("main:CRASH", "Renderer process gone:", t);
1211
- }), S.webContents.on("did-fail-load", (s, t, e) => {
1278
+ }), E.webContents.on("did-fail-load", (s, t, e) => {
1212
1279
  $("main:LOAD_ERROR", `Failed to load: ${t} ${e}`);
1213
1280
  });
1214
- const o = process.env.VITE_DEV_SERVER_URL;
1215
- if (o)
1216
- $("main", `Loading dev server URL: ${o}`), S.loadURL(o);
1281
+ const n = process.env.VITE_DEV_SERVER_URL;
1282
+ if (n)
1283
+ $("main", `Loading dev server URL: ${n}`), E.loadURL(n);
1217
1284
  else {
1218
- const s = d(J, "../dist-renderer/index.html");
1219
- $("main", `Loading file: ${s}`), S.loadFile(s);
1285
+ const s = u(J, "../dist-renderer/index.html");
1286
+ $("main", `Loading file: ${s}`), E.loadFile(s);
1220
1287
  }
1221
- process.env.VITE_DEV_SERVER_URL && S.webContents.openDevTools(), S.on("closed", () => {
1222
- S = null;
1288
+ process.env.VITE_DEV_SERVER_URL && E.webContents.openDevTools(), E.on("closed", () => {
1289
+ E = null;
1223
1290
  });
1224
1291
  }
1225
- pe.registerSchemesAsPrivileged([
1292
+ fe.registerSchemesAsPrivileged([
1226
1293
  { scheme: "council-file", privileges: { bypassCSP: !0, supportFetchAPI: !0 } }
1227
1294
  ]);
1228
- L.whenReady().then(() => {
1295
+ D.whenReady().then(() => {
1229
1296
  $("main", "App ready, registering IPC handlers");
1230
- const o = "State Change Council", s = [
1297
+ const n = "State Change Council", s = [
1231
1298
  {
1232
- label: o,
1299
+ label: n,
1233
1300
  submenu: [
1234
- { role: "about", label: `About ${o}` },
1301
+ { role: "about", label: `About ${n}` },
1235
1302
  { type: "separator" },
1236
1303
  { role: "services" },
1237
1304
  { type: "separator" },
1238
- { role: "hide", label: `Hide ${o}` },
1305
+ { role: "hide", label: `Hide ${n}` },
1239
1306
  { role: "hideOthers" },
1240
1307
  { role: "unhide" },
1241
1308
  { type: "separator" },
1242
- { role: "quit", label: `Quit ${o}` }
1309
+ { role: "quit", label: `Quit ${n}` }
1243
1310
  ]
1244
1311
  },
1245
1312
  { role: "fileMenu" },
@@ -1247,13 +1314,13 @@ L.whenReady().then(() => {
1247
1314
  { role: "viewMenu" },
1248
1315
  { role: "windowMenu" }
1249
1316
  ];
1250
- ne.setApplicationMenu(ne.buildFromTemplate(s)), pe.handle("council-file", (t) => {
1317
+ ne.setApplicationMenu(ne.buildFromTemplate(s)), fe.handle("council-file", (t) => {
1251
1318
  const e = decodeURIComponent(t.url.replace("council-file://", ""));
1252
- return Se.fetch($e(e).href);
1253
- }), It(Ae, () => S), me(), L.on("activate", () => {
1254
- fe.getAllWindows().length === 0 && me();
1319
+ return Ae.fetch(Pe(e).href);
1320
+ }), Nt(Ie, () => E), pe(), D.on("activate", () => {
1321
+ he.getAllWindows().length === 0 && pe();
1255
1322
  });
1256
1323
  });
1257
- L.on("window-all-closed", () => {
1258
- process.platform !== "darwin" && L.quit();
1324
+ D.on("window-all-closed", () => {
1325
+ process.platform !== "darwin" && D.quit();
1259
1326
  });