@statechange/council 0.9.0 → 0.10.0

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