e3d-pilot 0.1.0 → 0.1.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.
- package/README.md +65 -12
- package/bin/e3d-mcp +94 -18
- package/lib/web/render.js +2 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
**Multi-model AI problem solving: independent answers → critique → convergence → human decision → execution → review → provenance.**
|
|
4
4
|
|
|
5
|
-

|
|
5
|
+

|
|
6
6
|
|
|
7
7
|
e3d-pilot is an open-source system for getting multiple AI models to work on the same problem instead of trusting a single model's answer — from a one-off question to a fully executed, human-gated software change.
|
|
8
8
|
|
|
9
|
-
It starts simply. Ask several configured models (Claude, Codex, Grok, Devin, or others you wire in) the same question. Let them answer independently, see and critique each other's positions, revise, expose real dissent, and converge on the strongest answer. That's `bin/e3d-debate` — a standalone tool, no repo or pipeline required.
|
|
9
|
+
It starts simply. Ask several configured models (Claude, Codex, Grok, Devin, or others you wire in) the same question. Let them answer independently, see and critique each other's positions, revise, expose real dissent, and converge on the strongest answer. That's `bin/e3d-debate` — a standalone tool, no repo or pipeline required. It's also available inside your editor as an MCP server: [use it from GitHub Copilot, Claude Code, or Cursor](#use-e3d-pilot-from-github-copilot-claude-code-and-cursor-mcp) with one `npx` line.
|
|
10
10
|
|
|
11
11
|
Then e3d-pilot takes the same instinct further, into real software work: `discover → ideate → approve → draft → negotiate → execute → review → publish → outcome`. Every step is preserved in an append-only ledger, so the system (or a human, or a fresh AI session) can later reconstruct where an idea came from, which models or people influenced it, what it produced, and what happened afterward.
|
|
12
12
|
|
|
@@ -70,13 +70,34 @@ demand instead — a real, named disagreement the tool didn't paper over.
|
|
|
70
70
|
|
|
71
71
|
You don't need the rest of e3d-pilot to use `e3d-debate` on its own — see [e3d-debate](#e3d-debate-experimental) below for the full flag reference. Keep reading for the part that takes a debate's answer all the way to a shipped, reviewed, provenance-tracked change.
|
|
72
72
|
|
|
73
|
-
## Use e3d-
|
|
73
|
+
## Use e3d-pilot from GitHub Copilot, Claude Code, and Cursor (MCP)
|
|
74
74
|
|
|
75
|
-
`
|
|
75
|
+
`e3d-mcp` is a zero-dependency [Model Context Protocol](https://modelcontextprotocol.io) server. It lets an AI coding assistant ask for a multi-model second opinion mid-task, instead of committing to the first answer one model gives it. It's published on npm as [`e3d-pilot`](https://www.npmjs.com/package/e3d-pilot) and listed in the official MCP registry as `io.github.spacepacket1/e3d-pilot`.
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
| Tool | What it does |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| `e3d_debate` | Several independent models answer and a synthesizer returns a decisive answer, a consensus level (`full` / `majority` / `split`), any named dissent, and each model's final position, as both readable text and structured data (`dissent.present`, `dissent.summary`, `positions`). `mode: "quick"` (default) is one round and takes seconds; `mode: "thorough"` adds a critique round where models rebut each other. The full transcript is opt-in (`include_transcript`). |
|
|
80
|
+
| `e3d_list_providers` | Shows which model providers are available on this machine. A debate needs at least two. |
|
|
81
|
+
| `e3d_provenance_trace` | Read-only: traces why a change exists, from its origin through debates, approvals, execution, review, and outcome. |
|
|
82
|
+
| `e3d_ideas_list` | Read-only: lists the ideas e3d-pilot tracks in a repo, to find an id for the trace. |
|
|
83
|
+
|
|
84
|
+
A real result in `quick` mode (11 seconds, two models), from asking whether a small CLI should default to a flat JSON file or SQLite:
|
|
85
|
+
|
|
86
|
+
```text
|
|
87
|
+
FINAL ANSWER: Default to a flat JSON file for a small CLI. It has no dependencies, is
|
|
88
|
+
human-readable and diffable, and is easy to hand-edit and debug. Write it atomically
|
|
89
|
+
(temp file, then rename). Move to SQLite once you need indexed queries, concurrent
|
|
90
|
+
writers, or data beyond a few MB.
|
|
91
|
+
CONSENSUS: split
|
|
92
|
+
DISSENT: Codex prefers SQLite as the default for any persistent mutable state because
|
|
93
|
+
it gives transactions, concurrency safety, and schema evolution at low cost.
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The disagreement is the point: you get the answer and the strongest case against it, instead of one model's confident guess.
|
|
97
|
+
|
|
98
|
+
### Install
|
|
78
99
|
|
|
79
|
-
**
|
|
100
|
+
**VS Code (GitHub Copilot agent mode):** [one-click install](https://insiders.vscode.dev/redirect/mcp/install?name=e3d-pilot&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22-p%22%2C%22e3d-pilot%22%2C%22e3d-mcp%22%5D%7D), or add this to `.vscode/mcp.json`:
|
|
80
101
|
|
|
81
102
|
```json
|
|
82
103
|
{
|
|
@@ -86,14 +107,37 @@ Tools: `e3d_debate` (answer + consensus + named dissent + transcript), `e3d_list
|
|
|
86
107
|
}
|
|
87
108
|
```
|
|
88
109
|
|
|
89
|
-
|
|
110
|
+
**Claude Code:**
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
claude mcp add e3d-pilot -- npx -y -p e3d-pilot e3d-mcp
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Cursor** (`~/.cursor/mcp.json`) and other clients that use the `mcpServers` shape:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{ "mcpServers": { "e3d-pilot": { "command": "npx", "args": ["-y", "-p", "e3d-pilot", "e3d-mcp"] } } }
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Any MCP client that can launch a stdio server should work; the shapes above are each client's standard format.
|
|
123
|
+
|
|
124
|
+
**Prerequisites:** Node 18+, `jq`, bash 4+ (macOS ships 3.2: `brew install bash jq`), and at least two model CLIs logged in (e.g. `claude` and `codex`). Check them all at once:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npx -y -p e3d-pilot e3d-mcp --doctor
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`--doctor` reports what's missing and how to fix it. It does not need the full pipeline's `e3d-code` executor (it only notes whether that is installed). From a clone, `bin/e3d-mcp --print-config --local` prints a config that points at your checkout.
|
|
90
131
|
|
|
91
|
-
|
|
132
|
+
### Using it
|
|
92
133
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
134
|
+
In Copilot agent mode (or any client), ask: *"Before you pick a storage layer, run e3d_debate on SQLite vs flat JSON for this CLI."* To make the assistant do this unprompted for contestable design choices, copy [examples/copilot-instructions.md](examples/copilot-instructions.md) into `.github/copilot-instructions.md` or `AGENTS.md`. Don't use it for lookups or questions with an obvious answer; it is slow and costs money.
|
|
135
|
+
|
|
136
|
+
### Cost and limits
|
|
137
|
+
|
|
138
|
+
- `quick` debates take seconds to a minute and `thorough` ones a few minutes; both spend **your** model credits. The server caps `e3d_debate` with `E3D_MCP_MAX_ROUNDS` (3), `E3D_MCP_MAX_PROVIDERS` (4), `E3D_MCP_MAX_CONCURRENT` (2), and `E3D_MCP_TIMEOUT_SEC` (900).
|
|
139
|
+
- It uses whatever provider CLIs and keys are in the server's environment (see `e3d-pilot providers list`).
|
|
140
|
+
- The Copilot *coding agent* runs on GitHub-hosted runners, where those provider CLIs aren't installed. Use the local (stdio) setup above for now.
|
|
97
141
|
- The server never writes to a repo. Recording a verdict into provenance is still `e3d-debate --repo --idea`.
|
|
98
142
|
|
|
99
143
|
## Why we built this
|
|
@@ -209,11 +253,18 @@ Start from [`examples/sample-fleet-config-with-ops.json`](examples/sample-fleet-
|
|
|
209
253
|
Install Bash, Git, `jq`, and `curl`; the full pipeline's execute stage also needs [`e3d-code`](https://github.com/spacepacket1/e3d-code) (formerly `spec2code`; the debate and MCP tools do not). Install and authenticate whichever model CLIs and forge CLI your configuration uses (`claude`, `codex`, `devin`, a local OpenAI-compatible endpoint, and/or `grok` for `grok-build`). Add this repository's `bin` directory to `PATH`:
|
|
210
254
|
|
|
211
255
|
```bash
|
|
256
|
+
# From npm (also installs e3d-code, the execute-stage dependency, when it can):
|
|
257
|
+
npm install -g e3d-pilot
|
|
258
|
+
e3d-pilot --help
|
|
259
|
+
|
|
260
|
+
# Or from a clone:
|
|
212
261
|
git clone https://github.com/spacepacket1/e3d-pilot.git
|
|
213
262
|
export PATH="$PWD/e3d-pilot/bin:$PATH"
|
|
214
263
|
e3d-pilot --help
|
|
215
264
|
```
|
|
216
265
|
|
|
266
|
+
The npm package provides three commands: `e3d-pilot`, `e3d-debate`, and `e3d-mcp`.
|
|
267
|
+
|
|
217
268
|
Node.js is only needed for the optional `e3d-pilot web` dashboard (see [Web UI](#web-ui-optional) below) — every other command needs only Bash, Git, `jq`, and `curl`.
|
|
218
269
|
|
|
219
270
|
## Configure a repository
|
|
@@ -578,6 +629,8 @@ Both `E3D_PILOT_WEB_AUTH_USER` and `E3D_PILOT_WEB_AUTH_PASS` are required; there
|
|
|
578
629
|
|
|
579
630
|
## e3d-debate (experimental)
|
|
580
631
|
|
|
632
|
+
> Want this inside your editor? `e3d-debate` is also exposed as an [MCP tool](#use-e3d-pilot-from-github-copilot-claude-code-and-cursor-mcp) for Copilot, Claude Code, and Cursor.
|
|
633
|
+
|
|
581
634
|
`bin/e3d-debate` is a separate, standalone script — not wired into `bin/e3d-pilot`'s subcommand tree or `config.schema.json`, the same way `bin/e3d-backend-benchmark` and `bin/e3d-grok-workers` already aren't — for the "ask several models a question and see whether they actually agree" loop, applied to any question rather than repo work specifically. It reuses the same `lib/providers/<name>` adapters and the `lib/negotiate/convergence` primitive that discover/ideate/negotiate/review already depend on. "Experimental" here means still standalone, not wired into the pipeline proper — not unused or untested: `tests/phase34.sh` covers the provenance-linking flags below, and it has already been the deciding mechanism behind several real architecture and product-direction calls for this project, not just a demo.
|
|
582
635
|
|
|
583
636
|
```bash
|
package/bin/e3d-mcp
CHANGED
|
@@ -22,7 +22,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
22
22
|
import { createInterface } from 'node:readline';
|
|
23
23
|
|
|
24
24
|
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
25
|
-
const SERVER_INFO = { name: 'e3d-pilot', version: '0.1.
|
|
25
|
+
const SERVER_INFO = { name: 'e3d-pilot', version: '0.1.1' };
|
|
26
26
|
const SUPPORTED_PROTOCOLS = ['2025-06-18', '2025-03-26', '2024-11-05'];
|
|
27
27
|
|
|
28
28
|
const envInt = (name, dflt) => {
|
|
@@ -63,26 +63,79 @@ async function configuredProviders() {
|
|
|
63
63
|
|
|
64
64
|
const NAME_RE = /^[a-z0-9][a-z0-9_-]*$/;
|
|
65
65
|
|
|
66
|
+
// quick = one round of independent answers + one synthesis call (fast, no cross-critique);
|
|
67
|
+
// thorough = the debate as originally shipped (2 rounds, models see and rebut each other).
|
|
68
|
+
const MODES = { quick: { rounds: 1, maxWords: 80 }, thorough: { rounds: 2, maxWords: 200 } };
|
|
69
|
+
|
|
70
|
+
// e3d-debate prints "FINAL ANSWER: ...\nCONSENSUS: ...\nDISSENT: ..." -- turn that into fields.
|
|
71
|
+
function parseVerdict(out) {
|
|
72
|
+
const raw = out.match(/FINAL ANSWER:[\s\S]*$/)?.[0]?.trim();
|
|
73
|
+
if (!raw) return null;
|
|
74
|
+
const m = raw.match(/^FINAL ANSWER:\s*([\s\S]*?)\s*\n\s*CONSENSUS:\s*([^\n]*)\n\s*DISSENT:\s*([\s\S]*)$/);
|
|
75
|
+
if (!m) return { final_answer: raw.replace(/^FINAL ANSWER:\s*/, ''), consensus: 'unknown', dissent: { present: null, summary: 'Verdict could not be parsed; read the answer text in full.' } };
|
|
76
|
+
const consensus = ['full', 'majority', 'split'].find((c) => m[2].toLowerCase().includes(c)) ?? 'unknown';
|
|
77
|
+
const dissentText = m[3].trim();
|
|
78
|
+
const none = /^["'`]*none[."'`]*$/i.test(dissentText);
|
|
79
|
+
return { final_answer: m[1].trim(), consensus, dissent: { present: !none, summary: none ? 'none' : dissentText } };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Each model's answer in the last round, from transcript.md ("## Round N -- provider").
|
|
83
|
+
function parsePositions(transcript) {
|
|
84
|
+
const sections = transcript.split(/\n(?=## )/).map((c) => c.match(/^## Round (\d+) -- (\S+)\n\n([\s\S]*)$/)).filter(Boolean)
|
|
85
|
+
.map((m) => ({ round: Number(m[1]), provider: m[2], body: m[3].trim() }));
|
|
86
|
+
const last = Math.max(0, ...sections.map((x) => x.round));
|
|
87
|
+
return {
|
|
88
|
+
roundsRan: last,
|
|
89
|
+
positions: sections.filter((x) => x.round === last).map((x) => ({
|
|
90
|
+
provider: x.provider,
|
|
91
|
+
text: x.body.replace(/\n*status: (approved|revise)\s*$/i, '').replace(/\n*POSITION:[^\n]*$/, '').trim(),
|
|
92
|
+
})),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function renderDebate(d) {
|
|
97
|
+
const label = d.consensus === 'full' ? 'FULL CONSENSUS' : d.consensus === 'majority' ? 'MAJORITY (some dissent)' : d.consensus === 'split' ? 'SPLIT' : 'CONSENSUS UNKNOWN';
|
|
98
|
+
const lines = [
|
|
99
|
+
`# Debate verdict: ${label}`,
|
|
100
|
+
`_${d.providers.join(', ')} | ${d.mode} mode | ${d.rounds_ran} round${d.rounds_ran === 1 ? '' : 's'}_`,
|
|
101
|
+
'', `**Answer:** ${d.final_answer}`, '',
|
|
102
|
+
];
|
|
103
|
+
if (d.dissent.present === false) lines.push('**Dissent:** none', '');
|
|
104
|
+
else lines.push(`**Dissent (report this to the user, do not drop it when summarizing):** ${d.dissent.summary}`, '');
|
|
105
|
+
if (d.positions.length) {
|
|
106
|
+
lines.push('**Each model\'s final position:**');
|
|
107
|
+
for (const p of d.positions) lines.push('', `**${p.provider}:**`, ...p.text.split('\n').map((l) => `> ${l}`));
|
|
108
|
+
}
|
|
109
|
+
if (d.transcript) lines.push('', '<details><summary>Full transcript</summary>', '', d.transcript, '', '</details>');
|
|
110
|
+
return lines.join('\n');
|
|
111
|
+
}
|
|
112
|
+
|
|
66
113
|
async function toolDebate(args, progress) {
|
|
67
114
|
const question = typeof args.question === 'string' ? args.question.trim() : '';
|
|
68
115
|
if (!question) return text('error: `question` is required', true);
|
|
69
116
|
if (question.length > LIMITS.questionChars) return text(`error: question exceeds ${LIMITS.questionChars} characters`, true);
|
|
70
117
|
|
|
71
|
-
const
|
|
118
|
+
const mode = args.mode ?? 'quick';
|
|
119
|
+
if (!MODES[mode]) return text('error: mode must be "quick" or "thorough"', true);
|
|
120
|
+
const rounds = args.rounds ?? MODES[mode].rounds;
|
|
72
121
|
if (!Number.isInteger(rounds) || rounds < 1 || rounds > LIMITS.rounds) return text(`error: rounds must be an integer 1..${LIMITS.rounds}`, true);
|
|
73
|
-
const maxWords = args.max_words ??
|
|
122
|
+
const maxWords = args.max_words ?? MODES[mode].maxWords;
|
|
74
123
|
if (!Number.isInteger(maxWords) || maxWords < 20 || maxWords > LIMITS.maxWords) return text(`error: max_words must be an integer 20..${LIMITS.maxWords}`, true);
|
|
124
|
+
if (args.include_transcript !== undefined && typeof args.include_transcript !== 'boolean') return text('error: include_transcript must be a boolean', true);
|
|
75
125
|
|
|
76
|
-
|
|
126
|
+
// E3D_MCP_DEBATE_BIN swaps in another debate executable (used by tests); provider checks are then that binary's job.
|
|
127
|
+
const debateBin = process.env.E3D_MCP_DEBATE_BIN || join(ROOT, 'bin/e3d-debate');
|
|
128
|
+
const stubbed = Boolean(process.env.E3D_MCP_DEBATE_BIN);
|
|
129
|
+
const known = stubbed ? [] : await configuredProviders();
|
|
77
130
|
let providers = args.providers;
|
|
78
131
|
if (providers === undefined) {
|
|
79
|
-
providers = known.filter((p) => p.available && ['claude', 'codex'].includes(p.name)).map((p) => p.name);
|
|
132
|
+
providers = stubbed ? ['claude', 'codex'] : known.filter((p) => p.available && ['claude', 'codex'].includes(p.name)).map((p) => p.name);
|
|
80
133
|
}
|
|
81
134
|
if (!Array.isArray(providers) || providers.some((p) => typeof p !== 'string' || !NAME_RE.test(p))) return text('error: providers must be an array of provider names', true);
|
|
82
135
|
providers = [...new Set(providers)];
|
|
83
136
|
if (providers.length < 2) return text('error: need at least 2 available providers to debate (call e3d_list_providers); only found: ' + (providers.join(', ') || 'none'), true);
|
|
84
137
|
if (providers.length > LIMITS.providers) return text(`error: at most ${LIMITS.providers} providers per debate`, true);
|
|
85
|
-
const unavailable = providers.filter((p) => !known.find((k) => k.name === p && k.available));
|
|
138
|
+
const unavailable = stubbed ? [] : providers.filter((p) => !known.find((k) => k.name === p && k.available));
|
|
86
139
|
if (unavailable.length) return text(`error: provider(s) not available on this machine: ${unavailable.join(', ')}`, true);
|
|
87
140
|
|
|
88
141
|
if (activeDebates >= LIMITS.concurrent) return text(`error: ${LIMITS.concurrent} debates already running; retry shortly`, true);
|
|
@@ -94,21 +147,23 @@ async function toolDebate(args, progress) {
|
|
|
94
147
|
const cliArgs = ['--question-file', qfile, '--providers', providers.join(','), '--rounds', String(rounds),
|
|
95
148
|
'--max-words', String(maxWords), '--out-dir', join(dir, 'out')];
|
|
96
149
|
let step = 0;
|
|
97
|
-
const r = await run(
|
|
150
|
+
const r = await run(debateBin, cliArgs, {
|
|
98
151
|
timeoutMs: LIMITS.timeoutMs,
|
|
99
152
|
onStderr: (s) => { for (const m of s.matchAll(/^=== (.+?) ===$/gm)) progress?.(++step, m[1]); },
|
|
100
153
|
});
|
|
101
154
|
if (r.timedOut) return text(`error: debate timed out after ${LIMITS.timeoutMs / 1000}s`, true);
|
|
102
|
-
const verdict = r.out
|
|
155
|
+
const verdict = parseVerdict(r.out);
|
|
103
156
|
if (r.code !== 0 || !verdict) return text(`error: debate failed (exit ${r.code})\n${r.err.slice(-1500)}`, true);
|
|
104
157
|
|
|
105
|
-
const transcript =
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
158
|
+
const transcript = await readFile(join(dir, 'out', 'transcript.md'), 'utf8').catch(() => '');
|
|
159
|
+
const { roundsRan, positions } = parsePositions(transcript);
|
|
160
|
+
const result = {
|
|
161
|
+
...verdict,
|
|
162
|
+
mode, providers, rounds_requested: rounds, rounds_ran: roundsRan || rounds,
|
|
163
|
+
positions,
|
|
164
|
+
...(args.include_transcript ? { transcript: transcript.trim() } : {}),
|
|
165
|
+
};
|
|
166
|
+
return { content: [{ type: 'text', text: renderDebate(result) }], structuredContent: result };
|
|
112
167
|
} finally {
|
|
113
168
|
activeDebates--;
|
|
114
169
|
rm(dir, { recursive: true, force: true }).catch(() => {});
|
|
@@ -157,18 +212,39 @@ async function toolIdeasList(args) {
|
|
|
157
212
|
const TOOLS = [
|
|
158
213
|
{
|
|
159
214
|
name: 'e3d_debate',
|
|
160
|
-
description: 'Get a multi-model second opinion on a decision or design question.
|
|
215
|
+
description: 'Get a multi-model second opinion on a decision or design question. Independent AI models answer, and a synthesizer returns a final answer, a consensus level (full/majority/split), and any dissent, plus each model\'s final position. ALWAYS report the dissent and consensus level to the user -- the disagreement is the point; never present a split verdict as settled. Write a self-contained question: the models cannot see your code. mode "quick" (default): one round, usually well under a minute. mode "thorough": models also critique each other, a few minutes. Use for architectural choices, tradeoffs, and contestable judgment calls -- not for lookups or questions with an obvious answer. Spends the user\'s model credits.',
|
|
161
216
|
inputSchema: {
|
|
162
217
|
type: 'object',
|
|
163
218
|
properties: {
|
|
164
219
|
question: { type: 'string', description: 'The decision or question, with enough context for models that cannot see your code.' },
|
|
220
|
+
mode: { type: 'string', enum: ['quick', 'thorough'], description: 'quick (default): 1 round, short answers, no cross-critique. thorough: 2 rounds where models see and rebut each other.' },
|
|
165
221
|
providers: { type: 'array', items: { type: 'string' }, description: 'Provider names (see e3d_list_providers). Default: claude + codex.' },
|
|
166
|
-
rounds: { type: 'integer', minimum: 1, maximum: LIMITS.rounds, description: '
|
|
167
|
-
max_words: { type: 'integer', minimum: 20, maximum: LIMITS.maxWords, description: '
|
|
222
|
+
rounds: { type: 'integer', minimum: 1, maximum: LIMITS.rounds, description: 'Override the mode\'s round count. Stops early on convergence.' },
|
|
223
|
+
max_words: { type: 'integer', minimum: 20, maximum: LIMITS.maxWords, description: 'Override the mode\'s per-provider per-round answer cap.' },
|
|
224
|
+
include_transcript: { type: 'boolean', description: 'Also return the full transcript (large). Default false.' },
|
|
168
225
|
},
|
|
169
226
|
required: ['question'],
|
|
170
227
|
additionalProperties: false,
|
|
171
228
|
},
|
|
229
|
+
outputSchema: {
|
|
230
|
+
type: 'object',
|
|
231
|
+
properties: {
|
|
232
|
+
final_answer: { type: 'string' },
|
|
233
|
+
consensus: { type: 'string', enum: ['full', 'majority', 'split', 'unknown'] },
|
|
234
|
+
dissent: {
|
|
235
|
+
type: 'object',
|
|
236
|
+
properties: { present: { type: ['boolean', 'null'] }, summary: { type: 'string', description: '"none" when there is no dissent.' } },
|
|
237
|
+
required: ['present', 'summary'],
|
|
238
|
+
},
|
|
239
|
+
positions: { type: 'array', items: { type: 'object', properties: { provider: { type: 'string' }, text: { type: 'string' } }, required: ['provider', 'text'] } },
|
|
240
|
+
mode: { type: 'string' },
|
|
241
|
+
providers: { type: 'array', items: { type: 'string' } },
|
|
242
|
+
rounds_requested: { type: 'integer' },
|
|
243
|
+
rounds_ran: { type: 'integer' },
|
|
244
|
+
transcript: { type: 'string' },
|
|
245
|
+
},
|
|
246
|
+
required: ['final_answer', 'consensus', 'dissent', 'positions', 'mode', 'providers', 'rounds_ran'],
|
|
247
|
+
},
|
|
172
248
|
annotations: { title: 'Multi-model debate', readOnlyHint: true, openWorldHint: true, idempotentHint: false },
|
|
173
249
|
},
|
|
174
250
|
{
|
package/lib/web/render.js
CHANGED
|
@@ -71,7 +71,6 @@ export function renderIdeasList(rows, filters, repoAliases) {
|
|
|
71
71
|
.map(
|
|
72
72
|
(r) => `<tr>
|
|
73
73
|
<td>${escapeHtml(r.repoAlias)}</td>
|
|
74
|
-
<td><code>${escapeHtml(r.idea_id)}</code></td>
|
|
75
74
|
<td><a href="/ideas/${encodeURIComponent(r.repoAlias)}/${encodeURIComponent(r.idea_id)}">${escapeHtml(r.title)}</a></td>
|
|
76
75
|
<td>${statusBadge(r.status)}</td>
|
|
77
76
|
<td>${escapeHtml(r.scores?.revenue ?? '-')} / ${escapeHtml(r.scores?.attraction ?? '-')} / ${escapeHtml(r.scores?.retention ?? '-')}</td>
|
|
@@ -88,8 +87,8 @@ export function renderIdeasList(rows, filters, repoAliases) {
|
|
|
88
87
|
<button type="submit">Filter</button>
|
|
89
88
|
</form>
|
|
90
89
|
<table>
|
|
91
|
-
<thead><tr><th>Workspace</th><th>
|
|
92
|
-
<tbody>${tableRows || '<tr><td colspan="
|
|
90
|
+
<thead><tr><th>Workspace</th><th>Title</th><th>Status</th><th>Rev/Attr/Ret</th><th>Effort</th><th>Updated</th></tr></thead>
|
|
91
|
+
<tbody>${tableRows || '<tr><td colspan="6">No ideas found.</td></tr>'}</tbody>
|
|
93
92
|
</table>`;
|
|
94
93
|
}
|
|
95
94
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "e3d-pilot",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Multi-model debate and decision provenance for AI coding work. Includes an MCP server (e3d-mcp) for GitHub Copilot, Claude Code, Cursor and other MCP clients.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|