@jigyasudham/veto 0.8.3 → 1.0.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.
- package/README.md +209 -52
- package/dist/agents/executor.js +36 -3
- package/dist/cli.js +246 -7
- package/dist/context/reader.js +113 -0
- package/dist/council/index.js +3 -1
- package/dist/plugins/loader.js +49 -0
- package/dist/router/index.js +2 -2
- package/dist/router/learning-updater.js +45 -1
- package/dist/server.js +478 -14
- package/dist/watcher/index.js +77 -0
- package/dist/workflow/pipeline.js +64 -0
- package/package.json +12 -3
- package/.claude/settings.local.json +0 -9
- package/src/adapters/claude.ts +0 -70
- package/src/adapters/codex.ts +0 -71
- package/src/adapters/gemini.ts +0 -71
- package/src/adapters/index.ts +0 -217
- package/src/agents/development/api.ts +0 -120
- package/src/agents/development/backend.ts +0 -85
- package/src/agents/development/coder.ts +0 -213
- package/src/agents/development/database.ts +0 -83
- package/src/agents/development/debugger.ts +0 -238
- package/src/agents/development/devops.ts +0 -86
- package/src/agents/development/frontend.ts +0 -85
- package/src/agents/development/migration.ts +0 -144
- package/src/agents/development/performance.ts +0 -144
- package/src/agents/development/refactor.ts +0 -86
- package/src/agents/development/reviewer.ts +0 -268
- package/src/agents/development/tester.ts +0 -151
- package/src/agents/executor.ts +0 -158
- package/src/agents/memory/context-manager.ts +0 -171
- package/src/agents/memory/decision-logger.ts +0 -160
- package/src/agents/memory/knowledge-base.ts +0 -124
- package/src/agents/memory/pattern-learner.ts +0 -143
- package/src/agents/memory/project-mapper.ts +0 -118
- package/src/agents/quality/accessibility.ts +0 -99
- package/src/agents/quality/code-quality.ts +0 -115
- package/src/agents/quality/compatibility.ts +0 -58
- package/src/agents/quality/documentation.ts +0 -105
- package/src/agents/quality/error-handling.ts +0 -96
- package/src/agents/research/competitor-analyzer.ts +0 -45
- package/src/agents/research/cost-analyzer.ts +0 -54
- package/src/agents/research/estimator.ts +0 -60
- package/src/agents/research/ethics-bias.ts +0 -113
- package/src/agents/research/researcher.ts +0 -114
- package/src/agents/research/risk-assessor.ts +0 -63
- package/src/agents/research/tech-advisor.ts +0 -55
- package/src/agents/security/auth.ts +0 -287
- package/src/agents/security/dependency-audit.ts +0 -337
- package/src/agents/security/penetration.ts +0 -262
- package/src/agents/security/privacy.ts +0 -285
- package/src/agents/security/scanner.ts +0 -322
- package/src/agents/security/secrets.ts +0 -249
- package/src/agents/types.ts +0 -66
- package/src/agents/workflow/automation.ts +0 -59
- package/src/agents/workflow/file-manager.ts +0 -52
- package/src/agents/workflow/git-agent.ts +0 -55
- package/src/agents/workflow/reporter.ts +0 -51
- package/src/agents/workflow/search-agent.ts +0 -40
- package/src/agents/workflow/task-coordinator.ts +0 -41
- package/src/agents/workflow/task-planner.ts +0 -47
- package/src/cli.ts +0 -204
- package/src/council/decision-engine.ts +0 -171
- package/src/council/devil-advocate.ts +0 -116
- package/src/council/index.ts +0 -44
- package/src/council/lead-developer.ts +0 -118
- package/src/council/legal-compliance.ts +0 -152
- package/src/council/product-manager.ts +0 -102
- package/src/council/security.ts +0 -172
- package/src/council/system-architect.ts +0 -132
- package/src/council/types.ts +0 -33
- package/src/council/ux-designer.ts +0 -121
- package/src/memory/local.ts +0 -305
- package/src/memory/schema.ts +0 -174
- package/src/memory/sync.ts +0 -274
- package/src/router/complexity-scorer.ts +0 -96
- package/src/router/context-compressor.ts +0 -74
- package/src/router/index.ts +0 -60
- package/src/router/learning-updater.ts +0 -271
- package/src/router/model-selector.ts +0 -83
- package/src/router/rate-monitor.ts +0 -103
- package/src/server.ts +0 -1038
- package/src/skills/development/skill-api-design.ts +0 -329
- package/src/skills/development/skill-auth.ts +0 -271
- package/src/skills/development/skill-ci-cd.ts +0 -0
- package/src/skills/development/skill-crud.ts +0 -209
- package/src/skills/development/skill-db-schema.ts +0 -0
- package/src/skills/development/skill-docker.ts +0 -0
- package/src/skills/development/skill-env-setup.ts +0 -0
- package/src/skills/development/skill-scaffold.ts +0 -323
- package/src/skills/intelligence/skill-complexity-score.ts +0 -69
- package/src/skills/intelligence/skill-cost-track.ts +0 -39
- package/src/skills/intelligence/skill-learning-loop.ts +0 -69
- package/src/skills/intelligence/skill-pattern-detect.ts +0 -38
- package/src/skills/intelligence/skill-rate-watch.ts +0 -61
- package/src/skills/memory/skill-context-compress.ts +0 -98
- package/src/skills/memory/skill-cross-sync.ts +0 -104
- package/src/skills/memory/skill-decision-log.ts +0 -119
- package/src/skills/memory/skill-session-restore.ts +0 -59
- package/src/skills/memory/skill-session-save.ts +0 -94
- package/src/skills/quality/skill-accessibility.ts +0 -0
- package/src/skills/quality/skill-code-review.ts +0 -84
- package/src/skills/quality/skill-docs-gen.ts +0 -0
- package/src/skills/quality/skill-perf-audit.ts +0 -0
- package/src/skills/quality/skill-security-scan.ts +0 -91
- package/src/skills/quality/skill-test-suite.ts +0 -290
- package/src/skills/workflow/skill-deploy.ts +0 -0
- package/src/skills/workflow/skill-git-workflow.ts +0 -0
- package/src/skills/workflow/skill-rollback.ts +0 -0
- package/src/skills/workflow/skill-task-breakdown.ts +0 -0
- package/tsconfig.json +0 -20
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { watch } from 'node:fs';
|
|
2
|
+
import { extname, basename, resolve } from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
const sessions = new Map();
|
|
5
|
+
const EXT_RULES = [
|
|
6
|
+
[(f) => basename(f) === 'package.json', 'dependency-audit', 'veto_agent_plan', 'package.json changed — check for new CVEs or unwanted deps'],
|
|
7
|
+
[(f) => /\.(env|env\..+)$/.test(basename(f)), 'secrets', 'veto_secrets_scan', '.env file changed — scan for exposed credentials'],
|
|
8
|
+
[(f, e) => ['.sql', '.prisma'].includes(e), 'database', 'veto_agent_plan', 'Database schema file changed'],
|
|
9
|
+
[(f, e) => /\.(test|spec)\.(ts|js|tsx|jsx)$/.test(f), 'tester', 'veto_agent_plan', 'Test file changed — review test coverage'],
|
|
10
|
+
[(f, e) => ['.ts', '.tsx', '.js', '.jsx'].includes(e), 'code-quality', 'veto_code_review', 'Source file saved — code quality check recommended'],
|
|
11
|
+
[(f, e) => ['.yaml', '.yml', '.toml'].includes(e), 'devops', 'veto_agent_plan', 'Config file changed'],
|
|
12
|
+
[(f, e) => e === '.json' && basename(f) !== 'package.json', 'coder', 'veto_agent_plan', 'JSON config changed'],
|
|
13
|
+
];
|
|
14
|
+
function classify(file) {
|
|
15
|
+
const ext = extname(file).toLowerCase();
|
|
16
|
+
for (const [match, agent, tool, reason] of EXT_RULES) {
|
|
17
|
+
if (match(file, ext))
|
|
18
|
+
return { agent, tool, reason };
|
|
19
|
+
}
|
|
20
|
+
return { agent: 'coder', tool: 'veto_agent_plan', reason: 'File changed' };
|
|
21
|
+
}
|
|
22
|
+
export function startWatch(projectDir) {
|
|
23
|
+
const dir = resolve(projectDir);
|
|
24
|
+
const id = randomUUID().slice(0, 8);
|
|
25
|
+
const watcher = watch(dir, { recursive: true }, (eventType, filename) => {
|
|
26
|
+
if (!filename)
|
|
27
|
+
return;
|
|
28
|
+
// ignore node_modules, .git, dist
|
|
29
|
+
if (/node_modules|\.git|dist|\.veto/.test(filename))
|
|
30
|
+
return;
|
|
31
|
+
const session = sessions.get(id);
|
|
32
|
+
if (!session)
|
|
33
|
+
return;
|
|
34
|
+
const { agent, tool, reason } = classify(filename);
|
|
35
|
+
session.events.push({
|
|
36
|
+
event_type: eventType === 'rename' ? 'rename' : 'change',
|
|
37
|
+
file: filename,
|
|
38
|
+
ext: extname(filename).toLowerCase(),
|
|
39
|
+
triggered_at: new Date().toISOString(),
|
|
40
|
+
recommended_agent: agent,
|
|
41
|
+
suggested_tool: tool,
|
|
42
|
+
reason,
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
sessions.set(id, {
|
|
46
|
+
watcher,
|
|
47
|
+
project_dir: dir,
|
|
48
|
+
events: [],
|
|
49
|
+
started_at: new Date().toISOString(),
|
|
50
|
+
});
|
|
51
|
+
return id;
|
|
52
|
+
}
|
|
53
|
+
export function pollWatch(watchId) {
|
|
54
|
+
const session = sessions.get(watchId);
|
|
55
|
+
if (!session)
|
|
56
|
+
return { found: false, events: [] };
|
|
57
|
+
const events = [...session.events];
|
|
58
|
+
session.events = [];
|
|
59
|
+
return { found: true, events, project_dir: session.project_dir };
|
|
60
|
+
}
|
|
61
|
+
export function stopWatch(watchId) {
|
|
62
|
+
const session = sessions.get(watchId);
|
|
63
|
+
if (!session)
|
|
64
|
+
return false;
|
|
65
|
+
session.watcher.close();
|
|
66
|
+
sessions.delete(watchId);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
export function listWatches() {
|
|
70
|
+
return Array.from(sessions.entries()).map(([id, s]) => ({
|
|
71
|
+
id,
|
|
72
|
+
project_dir: s.project_dir,
|
|
73
|
+
started_at: s.started_at,
|
|
74
|
+
pending_events: s.events.length,
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { executeOne } from '../agents/executor.js';
|
|
2
|
+
export async function runPipeline(steps, globalProjectDir) {
|
|
3
|
+
const results = [];
|
|
4
|
+
const start = Date.now();
|
|
5
|
+
let stoppedAt;
|
|
6
|
+
let stopReason;
|
|
7
|
+
for (const step of steps) {
|
|
8
|
+
const result = await executeOne({
|
|
9
|
+
id: step.id,
|
|
10
|
+
agent: step.agent,
|
|
11
|
+
task: step.task,
|
|
12
|
+
code: step.code,
|
|
13
|
+
context: step.context,
|
|
14
|
+
project_dir: step.project_dir ?? globalProjectDir,
|
|
15
|
+
});
|
|
16
|
+
const confidence = result.output.confidence;
|
|
17
|
+
const confidencePct = Math.round(confidence * 100);
|
|
18
|
+
if (result.error) {
|
|
19
|
+
results.push({
|
|
20
|
+
id: step.id, agent: step.agent, status: 'error',
|
|
21
|
+
confidence: 0, severity: 'critical',
|
|
22
|
+
recommendation: result.error,
|
|
23
|
+
gate: step.gate, duration_ms: result.duration_ms, error: result.error,
|
|
24
|
+
});
|
|
25
|
+
stoppedAt = step.id;
|
|
26
|
+
stopReason = `Step "${step.id}" errored: ${result.error}`;
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
const gateFailed = step.gate !== undefined && confidencePct < step.gate;
|
|
30
|
+
results.push({
|
|
31
|
+
id: step.id, agent: step.agent,
|
|
32
|
+
status: gateFailed ? 'failed_gate' : 'passed',
|
|
33
|
+
confidence: confidencePct,
|
|
34
|
+
severity: result.output.severity,
|
|
35
|
+
recommendation: result.output.recommendation,
|
|
36
|
+
gate: step.gate,
|
|
37
|
+
duration_ms: result.duration_ms,
|
|
38
|
+
});
|
|
39
|
+
if (gateFailed) {
|
|
40
|
+
stoppedAt = step.id;
|
|
41
|
+
stopReason = `Step "${step.id}" confidence ${confidencePct}% below gate ${step.gate}%`;
|
|
42
|
+
// mark remaining steps as skipped
|
|
43
|
+
const remaining = steps.slice(steps.indexOf(step) + 1);
|
|
44
|
+
for (const s of remaining) {
|
|
45
|
+
results.push({ id: s.id, agent: s.agent, status: 'skipped', confidence: 0, severity: 'info', recommendation: 'Skipped — upstream gate failed', gate: s.gate, duration_ms: 0 });
|
|
46
|
+
}
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const passed = results.filter(r => r.status === 'passed').length;
|
|
51
|
+
const failed = results.filter(r => r.status === 'failed_gate' || r.status === 'error').length;
|
|
52
|
+
const verdict = failed === 0 ? 'passed' : passed > 0 ? 'partial' : 'failed';
|
|
53
|
+
return {
|
|
54
|
+
verdict,
|
|
55
|
+
steps_total: steps.length,
|
|
56
|
+
steps_passed: passed,
|
|
57
|
+
steps_failed: failed,
|
|
58
|
+
stopped_at: stoppedAt,
|
|
59
|
+
stop_reason: stopReason,
|
|
60
|
+
results,
|
|
61
|
+
total_duration_ms: Date.now() - start,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=pipeline.js.map
|
package/package.json
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jigyasudham/veto",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "50 agents.
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "50 agents. 34 tools. 3 AIs. Self-learning. Zero extra cost.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
7
7
|
"ai",
|
|
8
8
|
"claude",
|
|
9
9
|
"agents",
|
|
10
10
|
"memory",
|
|
11
|
-
"council"
|
|
11
|
+
"council",
|
|
12
|
+
"gemini",
|
|
13
|
+
"codex",
|
|
14
|
+
"cursor",
|
|
15
|
+
"windsurf",
|
|
16
|
+
"vscode",
|
|
17
|
+
"self-learning",
|
|
18
|
+
"router",
|
|
19
|
+
"workflow",
|
|
20
|
+
"code-review"
|
|
12
21
|
],
|
|
13
22
|
"author": "Jigyasu Dham <jigyasudham18@gmail.com>",
|
|
14
23
|
"license": "MIT",
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"PowerShell(Get-ChildItem D:\\\\Veto\\\\src -Recurse -File | Where-Object { $_.FullName -notmatch \"node_modules\" } | Select-Object FullName | ForEach-Object { $_.FullName.Replace\\(\"D:\\\\Veto\\\\\", \"\"\\) })",
|
|
5
|
-
"PowerShell(cd D:\\\\Veto; npm run build 2>&1; Write-Host \"Exit: $LASTEXITCODE\")",
|
|
6
|
-
"PowerShell(Get-ChildItem D:\\\\Veto\\\\docs -File | Select-Object FullName; Get-ChildItem D:\\\\Veto -File | Where-Object { $_.Extension -eq \".md\" } | Select-Object FullName)"
|
|
7
|
-
]
|
|
8
|
-
}
|
|
9
|
-
}
|
package/src/adapters/claude.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
// Claude Code adapter — connection config, rate signal detection, handoff format
|
|
2
|
-
|
|
3
|
-
export const PLATFORM = 'claude' as const;
|
|
4
|
-
|
|
5
|
-
export type AdapterSetup = {
|
|
6
|
-
platform: string;
|
|
7
|
-
mcp_config: object;
|
|
8
|
-
setup_steps: string[];
|
|
9
|
-
rate_limit_signals: string[];
|
|
10
|
-
continue_command: string;
|
|
11
|
-
notes: string[];
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export function getSetup(vetoServerPath: string): AdapterSetup {
|
|
15
|
-
return {
|
|
16
|
-
platform: 'claude',
|
|
17
|
-
mcp_config: {
|
|
18
|
-
mcpServers: {
|
|
19
|
-
veto: {
|
|
20
|
-
command: 'node',
|
|
21
|
-
args: [vetoServerPath],
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
setup_steps: [
|
|
26
|
-
`1. Build veto: npm run build`,
|
|
27
|
-
`2. Add to ~/.claude/mcp_servers.json:\n${JSON.stringify({ mcpServers: { veto: { command: 'node', args: [vetoServerPath] } } }, null, 2)}`,
|
|
28
|
-
`3. Restart Claude Code`,
|
|
29
|
-
`4. Verify: call veto_status — should return { "status": "running", "version": "0.7.0" }`,
|
|
30
|
-
],
|
|
31
|
-
rate_limit_signals: [
|
|
32
|
-
'rate limit exceeded',
|
|
33
|
-
'too many requests',
|
|
34
|
-
'429',
|
|
35
|
-
'overloaded',
|
|
36
|
-
'capacity',
|
|
37
|
-
'quota exceeded',
|
|
38
|
-
],
|
|
39
|
-
continue_command: 'veto_continue',
|
|
40
|
-
notes: [
|
|
41
|
-
'Claude Code connects to Veto via stdio MCP — the server runs as a child process',
|
|
42
|
-
'All veto_* tools are available natively in Claude Code once connected',
|
|
43
|
-
'Rate limits are tracked by Veto per day — call veto_rate_status to check headroom',
|
|
44
|
-
'Before hitting the limit: call veto_handoff to get a session ID and switch instructions',
|
|
45
|
-
],
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function detectRateLimit(errorText: string): boolean {
|
|
50
|
-
const signals = ['rate limit', 'too many requests', '429', 'overloaded', 'quota'];
|
|
51
|
-
return signals.some(s => errorText.toLowerCase().includes(s));
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function formatHandoffMessage(sessionId: string, targetPlatform: 'gemini' | 'codex'): string {
|
|
55
|
-
const target = targetPlatform === 'gemini' ? 'Gemini CLI' : 'Codex CLI';
|
|
56
|
-
return [
|
|
57
|
-
`Claude is approaching its rate limit. Switching to ${target}.`,
|
|
58
|
-
``,
|
|
59
|
-
`Session saved. ID: ${sessionId}`,
|
|
60
|
-
``,
|
|
61
|
-
`On ${target}, run:`,
|
|
62
|
-
` veto_continue`,
|
|
63
|
-
``,
|
|
64
|
-
`Veto will restore full context automatically. Nothing needs to be re-explained.`,
|
|
65
|
-
].join('\n');
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function formatSwitchPrompt(sessionId: string): string {
|
|
69
|
-
return `Rate limit approaching. Session saved (ID: ${sessionId}). Call veto_continue on the next platform to resume instantly.`;
|
|
70
|
-
}
|
package/src/adapters/codex.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
// Codex CLI adapter — connection config, rate signal detection, handoff format
|
|
2
|
-
// Codex CLI: https://github.com/openai/codex
|
|
3
|
-
|
|
4
|
-
export const PLATFORM = 'codex' as const;
|
|
5
|
-
|
|
6
|
-
export type AdapterSetup = {
|
|
7
|
-
platform: string;
|
|
8
|
-
mcp_config: object;
|
|
9
|
-
setup_steps: string[];
|
|
10
|
-
rate_limit_signals: string[];
|
|
11
|
-
continue_command: string;
|
|
12
|
-
notes: string[];
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export function getSetup(vetoServerPath: string): AdapterSetup {
|
|
16
|
-
return {
|
|
17
|
-
platform: 'codex',
|
|
18
|
-
mcp_config: {
|
|
19
|
-
mcpServers: {
|
|
20
|
-
veto: {
|
|
21
|
-
command: 'node',
|
|
22
|
-
args: [vetoServerPath],
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
setup_steps: [
|
|
27
|
-
`1. Install Codex CLI: npm install -g @openai/codex`,
|
|
28
|
-
`2. Set API key: export OPENAI_API_KEY=your-key`,
|
|
29
|
-
`3. Add to ~/.codex/config.json:\n${JSON.stringify({ mcpServers: { veto: { command: 'node', args: [vetoServerPath] } } }, null, 2)}`,
|
|
30
|
-
`4. Restart Codex CLI`,
|
|
31
|
-
`5. Verify: call veto_status — should return { "status": "running", "version": "0.7.0" }`,
|
|
32
|
-
],
|
|
33
|
-
rate_limit_signals: [
|
|
34
|
-
'rate limit reached',
|
|
35
|
-
'too many requests',
|
|
36
|
-
'429',
|
|
37
|
-
'insufficient quota',
|
|
38
|
-
'rate_limit_exceeded',
|
|
39
|
-
'quota_exceeded',
|
|
40
|
-
],
|
|
41
|
-
continue_command: 'veto_continue',
|
|
42
|
-
notes: [
|
|
43
|
-
'Codex CLI connects to Veto via stdio MCP — same server instance as Claude and Gemini',
|
|
44
|
-
'Config path: ~/.codex/config.json (created automatically on first run)',
|
|
45
|
-
'All veto_* tools work identically on Codex as on Claude and Gemini',
|
|
46
|
-
'Codex is the Tier 1/2 overflow when both Claude and Gemini are rate-limited',
|
|
47
|
-
'Uses GPT-4o / o4-mini depending on the tier assigned by the Veto router',
|
|
48
|
-
'ChatGPT web app does NOT support MCP — Codex CLI is the only OpenAI option',
|
|
49
|
-
],
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function detectRateLimit(errorText: string): boolean {
|
|
54
|
-
const signals = ['rate limit', 'too many requests', '429', 'insufficient quota', 'rate_limit_exceeded'];
|
|
55
|
-
return signals.some(s => errorText.toLowerCase().includes(s.toLowerCase()));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function formatHandoffMessage(sessionId: string, fromPlatform: 'claude' | 'gemini'): string {
|
|
59
|
-
const from = fromPlatform === 'claude' ? 'Claude' : 'Gemini';
|
|
60
|
-
return [
|
|
61
|
-
`${from} handed off to Codex. Session restored.`,
|
|
62
|
-
``,
|
|
63
|
-
`Session ID: ${sessionId}`,
|
|
64
|
-
``,
|
|
65
|
-
`Codex has full context. Continue the task as if nothing interrupted.`,
|
|
66
|
-
].join('\n');
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function formatContinueInstructions(): string {
|
|
70
|
-
return 'Run: veto_continue\nVeto will restore the most recent session automatically.';
|
|
71
|
-
}
|
package/src/adapters/gemini.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
// Gemini CLI adapter — connection config, rate signal detection, handoff format
|
|
2
|
-
// Gemini CLI MCP support: https://github.com/google-gemini/gemini-cli
|
|
3
|
-
|
|
4
|
-
export const PLATFORM = 'gemini' as const;
|
|
5
|
-
|
|
6
|
-
export type AdapterSetup = {
|
|
7
|
-
platform: string;
|
|
8
|
-
mcp_config: object;
|
|
9
|
-
setup_steps: string[];
|
|
10
|
-
rate_limit_signals: string[];
|
|
11
|
-
continue_command: string;
|
|
12
|
-
notes: string[];
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export function getSetup(vetoServerPath: string): AdapterSetup {
|
|
16
|
-
return {
|
|
17
|
-
platform: 'gemini',
|
|
18
|
-
mcp_config: {
|
|
19
|
-
mcpServers: {
|
|
20
|
-
veto: {
|
|
21
|
-
command: 'node',
|
|
22
|
-
args: [vetoServerPath],
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
setup_steps: [
|
|
27
|
-
`1. Install Gemini CLI: npm install -g @google/gemini-cli`,
|
|
28
|
-
`2. Authenticate: gemini auth`,
|
|
29
|
-
`3. Add to ~/.gemini/settings.json:\n${JSON.stringify({ mcpServers: { veto: { command: 'node', args: [vetoServerPath] } } }, null, 2)}`,
|
|
30
|
-
`4. Restart Gemini CLI`,
|
|
31
|
-
`5. Verify: call veto_status — should return { "status": "running", "version": "0.7.0" }`,
|
|
32
|
-
],
|
|
33
|
-
rate_limit_signals: [
|
|
34
|
-
'quota exceeded',
|
|
35
|
-
'resource exhausted',
|
|
36
|
-
'429',
|
|
37
|
-
'rate limit',
|
|
38
|
-
'too many requests',
|
|
39
|
-
'RESOURCE_EXHAUSTED',
|
|
40
|
-
],
|
|
41
|
-
continue_command: 'veto_continue',
|
|
42
|
-
notes: [
|
|
43
|
-
'Gemini CLI connects to Veto via stdio MCP — same server instance as Claude',
|
|
44
|
-
'Gemini CLI uses the same ~/.gemini/settings.json for MCP server config',
|
|
45
|
-
'All veto_* tools work identically on Gemini as on Claude',
|
|
46
|
-
'Gemini Flash is the default Tier 1/2 overflow platform when Claude is rate-limited',
|
|
47
|
-
'Free tier: 1,500 requests/day (15 RPM) — Veto tracks this in rate_usage table',
|
|
48
|
-
],
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function detectRateLimit(errorText: string): boolean {
|
|
53
|
-
const signals = ['quota exceeded', 'resource exhausted', '429', 'rate limit', 'RESOURCE_EXHAUSTED'];
|
|
54
|
-
return signals.some(s => errorText.toLowerCase().includes(s.toLowerCase()));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function formatHandoffMessage(sessionId: string, fromPlatform: 'claude' | 'codex'): string {
|
|
58
|
-
const from = fromPlatform === 'claude' ? 'Claude' : 'Codex';
|
|
59
|
-
return [
|
|
60
|
-
`${from} handed off to Gemini. Session restored.`,
|
|
61
|
-
``,
|
|
62
|
-
`Session ID: ${sessionId}`,
|
|
63
|
-
``,
|
|
64
|
-
`Gemini has full context. Continue the task as if nothing interrupted.`,
|
|
65
|
-
`Call veto_session_restore { "session_id": "${sessionId}" } if context needs refreshing.`,
|
|
66
|
-
].join('\n');
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function formatContinueInstructions(): string {
|
|
70
|
-
return 'Run: veto_continue\nVeto will restore the most recent session automatically.';
|
|
71
|
-
}
|
package/src/adapters/index.ts
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
// Handoff engine — platform detection, session save+switch, continue restore
|
|
2
|
-
|
|
3
|
-
import { getRateStatus, trackRequest } from '../router/rate-monitor.js';
|
|
4
|
-
import { saveSession, listSessions, restoreSession } from '../memory/local.js';
|
|
5
|
-
import type { Platform } from '../router/rate-monitor.js';
|
|
6
|
-
import * as claude from './claude.js';
|
|
7
|
-
import * as gemini from './gemini.js';
|
|
8
|
-
import * as codex from './codex.js';
|
|
9
|
-
|
|
10
|
-
export type HandoffResult = {
|
|
11
|
-
session_id: string;
|
|
12
|
-
saved_at: string;
|
|
13
|
-
from_platform: Platform;
|
|
14
|
-
to_platform: Platform;
|
|
15
|
-
reason: string;
|
|
16
|
-
rate_status: ReturnType<typeof getRateStatus>;
|
|
17
|
-
instructions: string;
|
|
18
|
-
one_liner: string;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export type ContinueResult = {
|
|
22
|
-
found: boolean;
|
|
23
|
-
session_id?: string;
|
|
24
|
-
platform?: string;
|
|
25
|
-
summary?: string;
|
|
26
|
-
context?: unknown;
|
|
27
|
-
task_state?: unknown;
|
|
28
|
-
next_action?: string;
|
|
29
|
-
project_dir?: string;
|
|
30
|
-
token_count?: number;
|
|
31
|
-
message: string;
|
|
32
|
-
restored_at: string;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export type PlatformSetupResult = {
|
|
36
|
-
platform: Platform;
|
|
37
|
-
mcp_config: object;
|
|
38
|
-
setup_steps: string[];
|
|
39
|
-
rate_limit_signals: string[];
|
|
40
|
-
continue_command: string;
|
|
41
|
-
notes: string[];
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
// ─── Handoff ──────────────────────────────────────────────────────────────────
|
|
45
|
-
|
|
46
|
-
export function handoff(options: {
|
|
47
|
-
summary?: string;
|
|
48
|
-
context?: string;
|
|
49
|
-
task_state?: string;
|
|
50
|
-
from_platform?: Platform;
|
|
51
|
-
to_platform?: Platform;
|
|
52
|
-
token_count?: number;
|
|
53
|
-
project_dir?: string;
|
|
54
|
-
}): HandoffResult {
|
|
55
|
-
const rateStatus = getRateStatus();
|
|
56
|
-
const from: Platform = options.from_platform ?? 'claude';
|
|
57
|
-
|
|
58
|
-
// Pick the best available target platform
|
|
59
|
-
const to: Platform = options.to_platform ?? selectTarget(from, rateStatus);
|
|
60
|
-
|
|
61
|
-
const reason = rateStatus[from].status === 'critical'
|
|
62
|
-
? `${from} is at ${rateStatus[from].used_percent}% of daily limit`
|
|
63
|
-
: rateStatus[from].status === 'warning'
|
|
64
|
-
? `${from} is at ${rateStatus[from].used_percent}% — switching proactively`
|
|
65
|
-
: 'Manual platform switch requested';
|
|
66
|
-
|
|
67
|
-
const { session_id, saved_at } = saveSession({
|
|
68
|
-
platform: from,
|
|
69
|
-
summary: options.summary ?? `Session handed off from ${from} to ${to}`,
|
|
70
|
-
context: options.context,
|
|
71
|
-
task_state: options.task_state,
|
|
72
|
-
token_count: options.token_count ?? 0,
|
|
73
|
-
project_dir: options.project_dir,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const instructions = buildInstructions(session_id, from, to, rateStatus);
|
|
77
|
-
const one_liner = `Session saved (${session_id.slice(0, 8)}…). On ${to}: call veto_continue`;
|
|
78
|
-
|
|
79
|
-
return {
|
|
80
|
-
session_id,
|
|
81
|
-
saved_at,
|
|
82
|
-
from_platform: from,
|
|
83
|
-
to_platform: to,
|
|
84
|
-
reason,
|
|
85
|
-
rate_status: rateStatus,
|
|
86
|
-
instructions,
|
|
87
|
-
one_liner,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// ─── Continue ─────────────────────────────────────────────────────────────────
|
|
92
|
-
|
|
93
|
-
export function continueSession(sessionId?: string): ContinueResult {
|
|
94
|
-
const now = new Date().toISOString();
|
|
95
|
-
|
|
96
|
-
if (sessionId) {
|
|
97
|
-
const result = restoreSession(sessionId);
|
|
98
|
-
if (!result.found || !result.session) {
|
|
99
|
-
return { found: false, message: `No session found with ID: ${sessionId}`, restored_at: now };
|
|
100
|
-
}
|
|
101
|
-
return buildContinueResult(result.session, now);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// No ID given — find the most recent session
|
|
105
|
-
const sessions = listSessions(1);
|
|
106
|
-
if (sessions.length === 0) {
|
|
107
|
-
return {
|
|
108
|
-
found: false,
|
|
109
|
-
message: 'No saved sessions found. Save a session first with veto_handoff or veto_session_save.',
|
|
110
|
-
restored_at: now,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const result = restoreSession(sessions[0].id);
|
|
115
|
-
if (!result.found || !result.session) {
|
|
116
|
-
return { found: false, message: 'Could not restore the most recent session.', restored_at: now };
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return buildContinueResult(result.session, now);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function buildContinueResult(session: ReturnType<typeof listSessions>[0], now: string): ContinueResult {
|
|
123
|
-
let context: unknown = null;
|
|
124
|
-
let task_state: unknown = null;
|
|
125
|
-
let next_action: string | undefined;
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
context = session.context ? JSON.parse(session.context) : null;
|
|
129
|
-
} catch { context = session.context; }
|
|
130
|
-
|
|
131
|
-
try {
|
|
132
|
-
let ts: unknown = session.task_state ? JSON.parse(session.task_state) : null;
|
|
133
|
-
// saveSession double-stringifies when given a pre-serialised string — unwrap if needed
|
|
134
|
-
if (typeof ts === 'string') { try { ts = JSON.parse(ts); } catch { /* keep as string */ } }
|
|
135
|
-
task_state = ts;
|
|
136
|
-
if (ts && typeof ts === 'object' && 'nextAction' in ts) {
|
|
137
|
-
next_action = String((ts as Record<string, unknown>)['nextAction']);
|
|
138
|
-
}
|
|
139
|
-
} catch { task_state = session.task_state; }
|
|
140
|
-
|
|
141
|
-
const message = [
|
|
142
|
-
`Session restored from ${session.platform} (saved ${session.started_at.slice(0, 16)}).`,
|
|
143
|
-
session.summary ? `\nSummary: ${session.summary}` : '',
|
|
144
|
-
next_action ? `\nNext action: ${next_action}` : '',
|
|
145
|
-
`\nContinue exactly where you left off. Nothing needs to be re-explained.`,
|
|
146
|
-
].filter(Boolean).join('');
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
found: true,
|
|
150
|
-
session_id: session.id,
|
|
151
|
-
platform: session.platform,
|
|
152
|
-
summary: session.summary ?? undefined,
|
|
153
|
-
context,
|
|
154
|
-
task_state,
|
|
155
|
-
next_action,
|
|
156
|
-
project_dir: session.project_dir ?? undefined,
|
|
157
|
-
token_count: session.token_count,
|
|
158
|
-
message,
|
|
159
|
-
restored_at: now,
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// ─── Platform Setup ───────────────────────────────────────────────────────────
|
|
164
|
-
|
|
165
|
-
export function getPlatformSetup(platform: Platform, vetoServerPath: string): PlatformSetupResult {
|
|
166
|
-
if (platform === 'claude') return claude.getSetup(vetoServerPath) as PlatformSetupResult;
|
|
167
|
-
if (platform === 'gemini') return gemini.getSetup(vetoServerPath) as PlatformSetupResult;
|
|
168
|
-
return codex.getSetup(vetoServerPath) as PlatformSetupResult;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
172
|
-
|
|
173
|
-
function selectTarget(from: Platform, rateStatus: ReturnType<typeof getRateStatus>): Platform {
|
|
174
|
-
const order: Platform[] = from === 'claude'
|
|
175
|
-
? ['gemini', 'codex']
|
|
176
|
-
: from === 'gemini'
|
|
177
|
-
? ['codex', 'claude']
|
|
178
|
-
: ['claude', 'gemini'];
|
|
179
|
-
|
|
180
|
-
for (const p of order) {
|
|
181
|
-
if (rateStatus[p].status !== 'critical') return p;
|
|
182
|
-
}
|
|
183
|
-
// All critical — pick the one with most headroom remaining
|
|
184
|
-
return order.reduce((best, p) =>
|
|
185
|
-
rateStatus[p].used_percent < rateStatus[best].used_percent ? p : best
|
|
186
|
-
, order[0]);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function buildInstructions(
|
|
190
|
-
sessionId: string,
|
|
191
|
-
from: Platform,
|
|
192
|
-
to: Platform,
|
|
193
|
-
rateStatus: ReturnType<typeof getRateStatus>
|
|
194
|
-
): string {
|
|
195
|
-
const toStatus = rateStatus[to];
|
|
196
|
-
const lines: string[] = [
|
|
197
|
-
`── Veto Handoff ──────────────────────────────────────────`,
|
|
198
|
-
`From: ${from.padEnd(8)} (${rateStatus[from].used_percent}% used)`,
|
|
199
|
-
`To: ${to.padEnd(8)} (${toStatus.used_percent}% used)`,
|
|
200
|
-
`Session: ${sessionId}`,
|
|
201
|
-
``,
|
|
202
|
-
`On ${to}:`,
|
|
203
|
-
` 1. Open a new terminal with ${to} CLI`,
|
|
204
|
-
` 2. Veto is already running — same server, same memory`,
|
|
205
|
-
` 3. Call: veto_continue`,
|
|
206
|
-
` Veto restores full context automatically.`,
|
|
207
|
-
` Nothing needs to be re-explained.`,
|
|
208
|
-
``,
|
|
209
|
-
`Or if you have the session ID:`,
|
|
210
|
-
` veto_continue { "session_id": "${sessionId}" }`,
|
|
211
|
-
``,
|
|
212
|
-
`Rate resets at: ${rateStatus[from].resets_at.slice(0, 16)} UTC`,
|
|
213
|
-
`─────────────────────────────────────────────────────────`,
|
|
214
|
-
];
|
|
215
|
-
|
|
216
|
-
return lines.join('\n');
|
|
217
|
-
}
|