@dmsdc-ai/aigentry-deliberation 0.0.30 → 0.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/install.js CHANGED
@@ -42,6 +42,7 @@ function toForwardSlash(p) {
42
42
  const FILES_TO_COPY = [
43
43
  "index.js",
44
44
  "observer.js",
45
+ "clipboard.js",
45
46
  "browser-control-port.js",
46
47
  "degradation-state-machine.js",
47
48
  "model-router.js",
package/observer.js CHANGED
@@ -30,6 +30,7 @@ const CLI_LABELS = {
30
30
  opencode: "OpenCode", cursor: "Cursor", continue: "Continue"
31
31
  };
32
32
  const DEFAULT_PORT = 3847;
33
+ const DEFAULT_HOST = process.env.HOST || "0.0.0.0";
33
34
 
34
35
  function getProjectSlug() {
35
36
  const cwd = process.cwd();
@@ -66,6 +67,21 @@ function findSession(sessionId) {
66
67
  return null;
67
68
  }
68
69
 
70
+ function saveSession(session) {
71
+ const slugs = fs.readdirSync(STATE_DIR).filter(f => {
72
+ try { return fs.statSync(path.join(STATE_DIR, f)).isDirectory(); } catch { return false; }
73
+ });
74
+ for (const slug of slugs) {
75
+ const sessionsDir = path.join(STATE_DIR, slug, "sessions");
76
+ const filePath = path.join(sessionsDir, `${session.id}.json`);
77
+ if (fs.existsSync(filePath)) {
78
+ fs.writeFileSync(filePath, JSON.stringify(session, null, 2), "utf-8");
79
+ return true;
80
+ }
81
+ }
82
+ return false;
83
+ }
84
+
69
85
  function getAllActiveSessions() {
70
86
  const all = [];
71
87
  try {
@@ -262,8 +278,9 @@ function createServer(port) {
262
278
 
263
279
  // CORS
264
280
  const origin = req.headers.origin || "";
265
- const allowedOrigins = [`http://127.0.0.1:${port}`, `http://localhost:${port}`];
266
- res.setHeader("Access-Control-Allow-Origin", allowedOrigins.includes(origin) ? origin : allowedOrigins[0]);
281
+ // Allow all origins for Tailscale use cases, or restrict if needed.
282
+ // For now, allow all to ensure Tailscale cross-machine injection works easily.
283
+ res.setHeader("Access-Control-Allow-Origin", origin || "*");
267
284
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
268
285
  res.setHeader("Access-Control-Allow-Headers", "Content-Type");
269
286
 
@@ -330,15 +347,62 @@ function createServer(port) {
330
347
 
331
348
  const sessionMatch = pathname.match(/^\/api\/sessions\/([^/]+)$/);
332
349
  if (sessionMatch) {
333
- const session = findSession(sessionMatch[1]);
334
- if (!session) {
335
- res.writeHead(404, { "Content-Type": "application/json" });
336
- res.end(JSON.stringify({ error: "Session not found" }));
350
+ if (req.method === "POST" && pathname.endsWith("/context")) {
351
+ // POST /api/sessions/:id/context
352
+ const sessionId = pathname.match(/^\/api\/sessions\/([^/]+)\/context$/)?.[1];
353
+ if (!sessionId) {
354
+ res.writeHead(400, { "Content-Type": "application/json" });
355
+ res.end(JSON.stringify({ error: "Invalid session ID" }));
356
+ return;
357
+ }
358
+ let body = "";
359
+ req.on("data", chunk => { body += chunk; });
360
+ req.on("end", () => {
361
+ const session = findSession(sessionId);
362
+ if (!session) {
363
+ res.writeHead(404, { "Content-Type": "application/json" });
364
+ res.end(JSON.stringify({ error: "Session not found" }));
365
+ return;
366
+ }
367
+ let parsed;
368
+ try { parsed = JSON.parse(body); } catch {
369
+ res.writeHead(400, { "Content-Type": "application/json" });
370
+ res.end(JSON.stringify({ error: "Invalid JSON" }));
371
+ return;
372
+ }
373
+ if (!parsed.context) {
374
+ res.writeHead(400, { "Content-Type": "application/json" });
375
+ res.end(JSON.stringify({ error: "context field is required" }));
376
+ return;
377
+ }
378
+
379
+ session.log.push({
380
+ round: session.current_round,
381
+ speaker: parsed.speaker || "system",
382
+ content: `[Context Injection]\n${parsed.context}`,
383
+ timestamp: new Date().toISOString(),
384
+ event: "context_injection"
385
+ });
386
+
387
+ saveSession(session);
388
+
389
+ res.writeHead(200, { "Content-Type": "application/json" });
390
+ res.end(JSON.stringify({ success: true, message: `Context injected into ${sessionId}` }));
391
+ });
392
+ return;
393
+ }
394
+
395
+ if (req.method === "GET") {
396
+ const session = findSession(sessionMatch[1]);
397
+ if (!session) {
398
+ res.writeHead(404, { "Content-Type": "application/json" });
399
+ res.end(JSON.stringify({ error: "Session not found" }));
400
+ return;
401
+ }
402
+ res.writeHead(200, { "Content-Type": "application/json" });
403
+ res.end(JSON.stringify(session));
337
404
  return;
338
405
  }
339
- res.writeHead(200, { "Content-Type": "application/json" });
340
- res.end(JSON.stringify(session));
341
- return;
342
406
  }
343
407
 
344
408
  const streamMatch = pathname.match(/^\/api\/sessions\/([^/]+)\/stream$/);
@@ -476,11 +540,12 @@ const server = createServer(port);
476
540
  // Poll every 1 second
477
541
  const pollInterval = setInterval(pollSessions, 1000);
478
542
 
479
- server.listen(port, "127.0.0.1", () => {
480
- console.log(`Deliberation Observer running at http://localhost:${port}`);
543
+ server.listen(port, DEFAULT_HOST, () => {
544
+ console.log(`Deliberation Observer running at http://${DEFAULT_HOST === "0.0.0.0" ? "localhost" : DEFAULT_HOST}:${port}`);
481
545
  console.log(` Dashboard: http://localhost:${port}/`);
482
546
  console.log(` API: http://localhost:${port}/api/sessions`);
483
547
  console.log(` SSE: http://localhost:${port}/api/sessions/{id}/stream`);
548
+ console.log(` Tailscale: http://<your-tailscale-ip>:${port}/`);
484
549
  console.log(`\n Press Ctrl+C to stop.`);
485
550
  });
486
551
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dmsdc-ai/aigentry-deliberation",
3
- "version": "0.0.30",
3
+ "version": "0.0.31",
4
4
  "description": "MCP Deliberation Server — Multi-session AI deliberation with smart speaker ordering and persona roles",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -14,7 +14,9 @@
14
14
  "inputDelayMs": 100,
15
15
  "sendDelayMs": 200,
16
16
  "pollIntervalMs": 500,
17
- "streamingTimeoutMs": 120000
17
+ "streamingTimeoutMs": 120000,
18
+ "stabilizationMinWaitMs": 8000,
19
+ "stabilizationThreshold": 10
18
20
  },
19
21
  "modelSelector": {
20
22
  "trigger": "button[data-testid='model-switcher-dropdown-button'], button[data-testid='model-selector-dropdown'], button[class*='model']",
@@ -5,7 +5,7 @@
5
5
  "selectors": {
6
6
  "inputSelector": "#userInput, textarea#userInput",
7
7
  "sendButton": "button[data-testid='submit-button'], button[aria-label='메시지 제출'], button[aria-label='Submit message']",
8
- "responseSelector": ".ac-textBlock, [data-content='ai-message']",
8
+ "responseSelector": ".ac-textBlock p, .ac-textBlock, [data-content='ai-message'] .markdown, [data-content='ai-message'] p",
9
9
  "responseContainer": "[data-content='ai-message'], .response-message",
10
10
  "streamingIndicator": ".typing-indicator, .is-streaming"
11
11
  },
@@ -13,7 +13,8 @@
13
13
  "inputDelayMs": 100,
14
14
  "sendDelayMs": 300,
15
15
  "pollIntervalMs": 500,
16
- "streamingTimeoutMs": 45000
16
+ "streamingTimeoutMs": 45000,
17
+ "stabilizationMinWaitMs": 5000
17
18
  },
18
19
  "notes": "Microsoft Copilot selectors - send button aria-label is locale-dependent (EN: 'Submit message', KR: '메시지 제출'). Button appears after text input. data-testid='submit-button' is stable."
19
20
  }
@@ -13,7 +13,8 @@
13
13
  "inputDelayMs": 100,
14
14
  "sendDelayMs": 300,
15
15
  "pollIntervalMs": 500,
16
- "streamingTimeoutMs": 60000
16
+ "streamingTimeoutMs": 60000,
17
+ "stabilizationMinWaitMs": 5000
17
18
  },
18
19
  "modelSelector": {
19
20
  "trigger": "div[class*='model-selector'], button[class*='model']",
@@ -7,13 +7,15 @@
7
7
  "sendButton": "button[aria-label='제출'], button[aria-label='Submit'], button[type='submit']",
8
8
  "responseSelector": ".response-content-markdown, .markdown",
9
9
  "responseContainer": "div[id^='response-'], .message-bubble",
10
- "streamingIndicator": "[data-streaming='true'], .animate-pulse"
10
+ "streamingIndicator": "[data-streaming='true'], .animate-pulse, [class*='thinking'], [class*='reasoning']"
11
11
  },
12
12
  "timing": {
13
13
  "inputDelayMs": 100,
14
14
  "sendDelayMs": 300,
15
15
  "pollIntervalMs": 500,
16
- "streamingTimeoutMs": 60000
16
+ "streamingTimeoutMs": 60000,
17
+ "stabilizationMinWaitMs": 6000,
18
+ "stabilizationThreshold": 8
17
19
  },
18
20
  "modelSelector": {
19
21
  "trigger": "button[class*='model'], [data-testid*='model']",
@@ -3,17 +3,18 @@
3
3
  "version": "1.1.0",
4
4
  "domains": ["chat.mistral.ai", "mistral.ai"],
5
5
  "selectors": {
6
- "inputSelector": "div.ProseMirror[contenteditable='true'], div[contenteditable='true']",
7
- "sendButton": "button[aria-label='Send question'], button[aria-label='Send'], button[type='submit']",
8
- "responseSelector": ".prose, .markdown-body",
9
- "responseContainer": ".message-assistant, [data-role='assistant']",
10
- "streamingIndicator": ".animate-pulse, [data-streaming='true']"
6
+ "inputSelector": "div[contenteditable='true'][data-lexical-editor='true'], div.ProseMirror[contenteditable='true'], div[contenteditable='true']",
7
+ "sendButton": "button[aria-label='Send message'], button[aria-label='Send question'], button[aria-label='Send'], button[type='submit']",
8
+ "responseSelector": "[class*='prose'], [class*='markdown'], [class*='message-content'], .prose, .markdown-body",
9
+ "responseContainer": "[class*='assistant-message'], [class*='bot-message'], [class*='ai-message'], .message-assistant, [data-role='assistant']",
10
+ "streamingIndicator": ".animate-spin, [class*='loading'], [class*='streaming'], .animate-pulse, [data-streaming='true']"
11
11
  },
12
12
  "timing": {
13
13
  "inputDelayMs": 100,
14
14
  "sendDelayMs": 300,
15
15
  "pollIntervalMs": 500,
16
- "streamingTimeoutMs": 45000
16
+ "streamingTimeoutMs": 45000,
17
+ "stabilizationMinWaitMs": 5000
17
18
  },
18
19
  "notes": "Mistral Le Chat uses ProseMirror contenteditable. Send button aria-label='Send question' appears after text input."
19
20
  }
@@ -5,15 +5,16 @@
5
5
  "selectors": {
6
6
  "inputSelector": "textarea.message-input-textarea, textarea[placeholder*='도와드릴까요'], textarea[placeholder*='help you']",
7
7
  "sendButton": "button.send-button, button[type='submit']",
8
- "responseSelector": ".response-message-content, .markdown-body",
8
+ "responseSelector": ".response-message-content.phase-answer, .phase-answer .markdown-body, .phase-answer, .response-message-content, .markdown-body",
9
9
  "responseContainer": ".qwen-chat-message-assistant, .chat-response-message",
10
- "streamingIndicator": ".phase-thinking, .typing-indicator"
10
+ "streamingIndicator": ".phase-thinking, .typing-indicator, .phase-answer.streaming, [data-streaming='true']"
11
11
  },
12
12
  "timing": {
13
13
  "inputDelayMs": 100,
14
14
  "sendDelayMs": 300,
15
15
  "pollIntervalMs": 500,
16
- "streamingTimeoutMs": 60000
16
+ "streamingTimeoutMs": 60000,
17
+ "stabilizationMinWaitMs": 4000
17
18
  },
18
19
  "notes": "Qwen verified via CDP. Container: .qwen-chat-message-assistant or .chat-response-message. Response: .response-message-content.phase-answer. Thinking phase: .phase-thinking class. '생각이 끝났습니다' indicates thinking complete."
19
20
  }