@backburner/cli 0.1.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/LICENSE +21 -0
- package/README.md +329 -0
- package/dist/src/agents/antigravity-launch.js +162 -0
- package/dist/src/agents/antigravity.js +251 -0
- package/dist/src/agents/claude-launch.js +117 -0
- package/dist/src/agents/claude.js +271 -0
- package/dist/src/agents/codex-launch.js +82 -0
- package/dist/src/agents/codex.js +261 -0
- package/dist/src/agents/composite-runner.js +13 -0
- package/dist/src/agents/extraction.js +442 -0
- package/dist/src/agents/failure-feedback.js +132 -0
- package/dist/src/agents/gemini-launch.js +96 -0
- package/dist/src/agents/gemini.js +392 -0
- package/dist/src/agents/job-agent-assignments.js +180 -0
- package/dist/src/agents/lifecycle-feedback.js +147 -0
- package/dist/src/agents/limit-detector.js +83 -0
- package/dist/src/agents/opencode-launch.js +138 -0
- package/dist/src/agents/opencode.js +156 -0
- package/dist/src/agents/plan-breakdown-feedback.js +97 -0
- package/dist/src/agents/pool.js +168 -0
- package/dist/src/agents/prompt.js +462 -0
- package/dist/src/agents/provider-sessions.js +105 -0
- package/dist/src/agents/review-feedback.js +255 -0
- package/dist/src/agents/selection.js +113 -0
- package/dist/src/agents/service.js +1087 -0
- package/dist/src/agents/types.js +1 -0
- package/dist/src/agents/wave-assessment-feedback.js +69 -0
- package/dist/src/attention/derive.js +337 -0
- package/dist/src/capabilities/index.js +2 -0
- package/dist/src/capabilities/projection.js +183 -0
- package/dist/src/capabilities/types.js +1 -0
- package/dist/src/capabilities/validator.js +87 -0
- package/dist/src/cli/commands/broker-smoke.js +96 -0
- package/dist/src/cli/commands/broker.js +56 -0
- package/dist/src/cli/commands/init.js +4 -0
- package/dist/src/cli/commands/journal.js +43 -0
- package/dist/src/cli/commands/run-loop.js +56 -0
- package/dist/src/cli/commands/run.js +1241 -0
- package/dist/src/cli/commands/tui.js +187 -0
- package/dist/src/cli/dispatcher.js +51 -0
- package/dist/src/cli/errors.js +2 -0
- package/dist/src/cli/init.js +92 -0
- package/dist/src/cli/options.js +86 -0
- package/dist/src/cli/output.js +231 -0
- package/dist/src/cli/run-journal.js +342 -0
- package/dist/src/cli/run-types.js +1 -0
- package/dist/src/cli/run.js +32 -0
- package/dist/src/cli/runtime/dispatch-lifecycle.js +772 -0
- package/dist/src/cli/runtime/provider-runtime-types.js +1 -0
- package/dist/src/cli/runtime/provider-runtime.js +152 -0
- package/dist/src/cli/runtime/provider-tools.js +73 -0
- package/dist/src/cli/runtime/providers/antigravity.js +26 -0
- package/dist/src/cli/runtime/providers/claude.js +20 -0
- package/dist/src/cli/runtime/providers/codex.js +20 -0
- package/dist/src/cli/runtime/providers/gemini.js +20 -0
- package/dist/src/cli/runtime/providers/opencode.js +20 -0
- package/dist/src/cli/runtime/run-context.js +131 -0
- package/dist/src/cli/tui/index.js +3 -0
- package/dist/src/cli/tui/screen.js +388 -0
- package/dist/src/cli/tui/tracker.js +558 -0
- package/dist/src/cli/tui/types.js +1 -0
- package/dist/src/cli/tui/views.js +1006 -0
- package/dist/src/config/loader.js +136 -0
- package/dist/src/config/schemas.js +403 -0
- package/dist/src/config/types.js +1 -0
- package/dist/src/context/types.js +331 -0
- package/dist/src/domain/entities.js +1 -0
- package/dist/src/git/backburner-git-tool-service.js +680 -0
- package/dist/src/git/gitops-lifecycle.js +56 -0
- package/dist/src/git/merge-prep-workspace.js +357 -0
- package/dist/src/git/types.js +484 -0
- package/dist/src/github/broker-model-content.js +215 -0
- package/dist/src/github/broker-server.js +237 -0
- package/dist/src/github/broker.js +638 -0
- package/dist/src/github/composed.js +53 -0
- package/dist/src/github/gateway.js +478 -0
- package/dist/src/github/normalize.js +222 -0
- package/dist/src/github/pr-classification.js +73 -0
- package/dist/src/github/scoped-broker.js +35 -0
- package/dist/src/github/security.js +126 -0
- package/dist/src/github/service.js +332 -0
- package/dist/src/github/types.js +1 -0
- package/dist/src/github/utils.js +16 -0
- package/dist/src/journal/index.js +4 -0
- package/dist/src/journal/reader.js +62 -0
- package/dist/src/journal/renderer.js +50 -0
- package/dist/src/journal/service.js +72 -0
- package/dist/src/journal/types.js +1 -0
- package/dist/src/journal/writer.js +19 -0
- package/dist/src/mcps/bridge.js +166 -0
- package/dist/src/mcps/index.js +5 -0
- package/dist/src/mcps/reconcile.js +113 -0
- package/dist/src/mcps/smithery-client.js +186 -0
- package/dist/src/mcps/smithery.js +164 -0
- package/dist/src/mcps/types.js +1 -0
- package/dist/src/memory/json-store.js +71 -0
- package/dist/src/memory/prompt.js +16 -0
- package/dist/src/memory/scope.js +31 -0
- package/dist/src/memory/store.js +1 -0
- package/dist/src/memory/tool-service.js +142 -0
- package/dist/src/memory/types.js +12 -0
- package/dist/src/onboarding/config-writer.js +96 -0
- package/dist/src/onboarding/engine.js +86 -0
- package/dist/src/onboarding/prompt.js +102 -0
- package/dist/src/onboarding/services/provider-discovery.js +58 -0
- package/dist/src/onboarding/services/repo-discovery.js +246 -0
- package/dist/src/onboarding/steps/check-github-auth.js +32 -0
- package/dist/src/onboarding/steps/check-requirements.js +34 -0
- package/dist/src/onboarding/steps/configure-models.js +105 -0
- package/dist/src/onboarding/steps/configure-paths.js +33 -0
- package/dist/src/onboarding/steps/discover-providers.js +38 -0
- package/dist/src/onboarding/steps/discover-repos.js +39 -0
- package/dist/src/onboarding/steps/generate-config.js +106 -0
- package/dist/src/onboarding/steps/readiness-validation.js +158 -0
- package/dist/src/onboarding/steps/select-repos.js +76 -0
- package/dist/src/onboarding/steps/show-summary.js +97 -0
- package/dist/src/onboarding/steps/welcome.js +27 -0
- package/dist/src/onboarding/types.js +1 -0
- package/dist/src/onboarding/validators/git-access.js +63 -0
- package/dist/src/onboarding/validators/github-auth.js +85 -0
- package/dist/src/onboarding/validators/tool-checker.js +153 -0
- package/dist/src/onboarding/validators/workspace.js +83 -0
- package/dist/src/prompts/resolver.js +106 -0
- package/dist/src/retrospectives/candidates.js +224 -0
- package/dist/src/retrospectives/derive.js +169 -0
- package/dist/src/retrospectives/ingest.js +321 -0
- package/dist/src/retrospectives/management-prs.js +380 -0
- package/dist/src/retrospectives/proposals.js +199 -0
- package/dist/src/retrospectives/scopes.js +272 -0
- package/dist/src/retrospectives/scoring.js +171 -0
- package/dist/src/retrospectives/types.js +1 -0
- package/dist/src/state/loader.js +473 -0
- package/dist/src/state/types.js +15 -0
- package/dist/src/tasks/derivation/builders.js +70 -0
- package/dist/src/tasks/derivation/handlers/comment-response.js +169 -0
- package/dist/src/tasks/derivation/handlers/implement-plan.js +103 -0
- package/dist/src/tasks/derivation/handlers/index.js +54 -0
- package/dist/src/tasks/derivation/handlers/packet-pr-control.js +92 -0
- package/dist/src/tasks/derivation/handlers/packet-worktree.js +29 -0
- package/dist/src/tasks/derivation/handlers/plan-breakdown.js +45 -0
- package/dist/src/tasks/derivation/handlers/prepare-for-merge.js +91 -0
- package/dist/src/tasks/derivation/handlers/product-discovery.js +36 -0
- package/dist/src/tasks/derivation/handlers/retry-failed-task.js +125 -0
- package/dist/src/tasks/derivation/handlers/review-pr.js +28 -0
- package/dist/src/tasks/derivation/handlers/shared.js +24 -0
- package/dist/src/tasks/derivation/handlers/sync-parent-branch.js +100 -0
- package/dist/src/tasks/derivation/handlers/wave-assessment.js +223 -0
- package/dist/src/tasks/derivation/handlers/write-plan.js +101 -0
- package/dist/src/tasks/derivation/handlers/write-product-spec.js +49 -0
- package/dist/src/tasks/derivation/runner.js +18 -0
- package/dist/src/tasks/derivation/types.js +1 -0
- package/dist/src/tasks/derive.js +810 -0
- package/dist/src/tasks/mcp-skip.js +10 -0
- package/dist/src/tasks/retry.js +10 -0
- package/dist/src/tasks/task.js +184 -0
- package/dist/src/tasks/types.js +1 -0
- package/dist/src/utils/command-runner.js +192 -0
- package/dist/src/utils/json.js +38 -0
- package/dist/src/utils/logger.js +43 -0
- package/dist/src/utils/paths.js +38 -0
- package/dist/src/utils/slug.js +10 -0
- package/dist/src/workflows/registry.js +174 -0
- package/dist/src/workflows/triage.js +90 -0
- package/dist/src/workflows/workstream-machine/derive-events.js +121 -0
- package/dist/src/workflows/workstream-machine/evaluate.js +24 -0
- package/dist/src/workflows/workstream-machine/events.js +1 -0
- package/dist/src/workflows/workstream-machine/machine.js +355 -0
- package/dist/src/workflows/workstream-machine/selectors.js +19 -0
- package/dist/src/workflows/workstream-machine/types.js +1 -0
- package/dist/src/workstreams/branch-sync.js +87 -0
- package/dist/src/workstreams/derive.js +629 -0
- package/dist/src/workstreams/mcp-blockers.js +420 -0
- package/dist/src/workstreams/packet-candidates.js +42 -0
- package/dist/src/workstreams/packet-lifecycle-feedback.js +60 -0
- package/dist/src/workstreams/packet-lifecycle-report.js +199 -0
- package/dist/src/workstreams/packet-plan.js +597 -0
- package/dist/src/workstreams/packet-projections.js +190 -0
- package/dist/src/workstreams/parent-branch-sync-action.js +312 -0
- package/dist/src/workstreams/parent-branch-sync.js +148 -0
- package/dist/src/workstreams/phases.js +4 -0
- package/dist/src/workstreams/plan-breakdown-generator/export.js +19 -0
- package/dist/src/workstreams/plan-breakdown-generator/index.js +6 -0
- package/dist/src/workstreams/plan-breakdown-generator/inspect.js +50 -0
- package/dist/src/workstreams/plan-breakdown-generator/patch.js +139 -0
- package/dist/src/workstreams/plan-breakdown-generator/session.js +19 -0
- package/dist/src/workstreams/plan-breakdown-generator/store.js +27 -0
- package/dist/src/workstreams/plan-breakdown-generator/tools.js +407 -0
- package/dist/src/workstreams/plan-breakdown-generator/types.js +6 -0
- package/dist/src/workstreams/plan-breakdown-generator/validate.js +212 -0
- package/dist/src/workstreams/plan-breakdown-schema.js +175 -0
- package/dist/src/workstreams/utils.js +15 -0
- package/dist/src/workstreams/wave-assessment/checkpoint.js +172 -0
- package/dist/src/workstreams/wave-assessment/index.js +3 -0
- package/dist/src/workstreams/wave-assessment/session.js +84 -0
- package/dist/src/workstreams/wave-assessment/store.js +51 -0
- package/dist/src/workstreams/wave-assessment/tools.js +419 -0
- package/dist/src/workstreams/wave-assessment/types.js +1 -0
- package/dist/src/workstreams/wave-assessment/validate.js +78 -0
- package/dist/src/worktrees/service.js +839 -0
- package/dist/src/worktrees/workspace-ownership.js +34 -0
- package/dist/src/worktrees/workspace-recovery.js +177 -0
- package/package.json +61 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const SEVERITY_PREFIX = {
|
|
2
|
+
info: " ",
|
|
3
|
+
warning: "! ",
|
|
4
|
+
error: "✗ "
|
|
5
|
+
};
|
|
6
|
+
export function renderJournalEntries(entries, opts = {}) {
|
|
7
|
+
if (opts.json) {
|
|
8
|
+
return JSON.stringify(entries, null, 2) + "\n";
|
|
9
|
+
}
|
|
10
|
+
if (entries.length === 0) {
|
|
11
|
+
return "No journal entries found.\n";
|
|
12
|
+
}
|
|
13
|
+
return (entries
|
|
14
|
+
.map((e) => {
|
|
15
|
+
const prefix = SEVERITY_PREFIX[e.severity] ?? " ";
|
|
16
|
+
const ts = e.timestamp.replace("T", " ").replace(/\.\d+Z$/, "Z");
|
|
17
|
+
const scope = e.repoId ? `[${e.repoId}]` : `[${e.scope}:${e.scopeId.slice(0, 8)}]`;
|
|
18
|
+
const base = `${ts} ${prefix}${scope} ${e.summary}`;
|
|
19
|
+
if (!opts.verbose) {
|
|
20
|
+
return base;
|
|
21
|
+
}
|
|
22
|
+
const lines = [base];
|
|
23
|
+
lines.push(` category: ${e.category}`);
|
|
24
|
+
if (e.cycleId)
|
|
25
|
+
lines.push(` cycle: ${e.cycleId}`);
|
|
26
|
+
if (e.workstreamId)
|
|
27
|
+
lines.push(` ws: ${e.workstreamId}`);
|
|
28
|
+
if (e.executionId)
|
|
29
|
+
lines.push(` exec: ${e.executionId}`);
|
|
30
|
+
if (e.noActionReason)
|
|
31
|
+
lines.push(` reason: ${e.noActionReason}`);
|
|
32
|
+
if (e.expectedNextAction)
|
|
33
|
+
lines.push(` next: ${e.expectedNextAction}`);
|
|
34
|
+
if (e.stateTransition) {
|
|
35
|
+
lines.push(` from: ${e.stateTransition.from}`);
|
|
36
|
+
lines.push(` to: ${e.stateTransition.to}`);
|
|
37
|
+
if (e.stateTransition.trigger) {
|
|
38
|
+
lines.push(` trigger: ${e.stateTransition.trigger}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (e.observedState) {
|
|
42
|
+
lines.push(` observed: ${JSON.stringify(e.observedState)}`);
|
|
43
|
+
}
|
|
44
|
+
if (e.derivedState) {
|
|
45
|
+
lines.push(` derived: ${JSON.stringify(e.derivedState)}`);
|
|
46
|
+
}
|
|
47
|
+
return lines.join("\n");
|
|
48
|
+
})
|
|
49
|
+
.join("\n") + "\n");
|
|
50
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { JournalWriter } from "./writer.js";
|
|
2
|
+
import { JournalReader } from "./reader.js";
|
|
3
|
+
export class JournalService {
|
|
4
|
+
writer;
|
|
5
|
+
reader;
|
|
6
|
+
constructor(journalDir) {
|
|
7
|
+
this.writer = new JournalWriter(journalDir);
|
|
8
|
+
this.reader = new JournalReader(journalDir);
|
|
9
|
+
}
|
|
10
|
+
async write(entry) {
|
|
11
|
+
return this.writer.write(entry);
|
|
12
|
+
}
|
|
13
|
+
async query(q = {}) {
|
|
14
|
+
return this.reader.query(q);
|
|
15
|
+
}
|
|
16
|
+
async recordNoAction(opts) {
|
|
17
|
+
return this.write({
|
|
18
|
+
...(opts.cycleId !== undefined ? { cycleId: opts.cycleId } : {}),
|
|
19
|
+
scope: opts.scope,
|
|
20
|
+
scopeId: opts.scopeId,
|
|
21
|
+
...(opts.repoId !== undefined ? { repoId: opts.repoId } : {}),
|
|
22
|
+
...(opts.workstreamId !== undefined ? { workstreamId: opts.workstreamId } : {}),
|
|
23
|
+
...(opts.issueNumber !== undefined ? { issueNumber: opts.issueNumber } : {}),
|
|
24
|
+
...(opts.prNumber !== undefined ? { prNumber: opts.prNumber } : {}),
|
|
25
|
+
category: "no_action",
|
|
26
|
+
severity: "info",
|
|
27
|
+
summary: opts.reason,
|
|
28
|
+
noActionReason: opts.reason,
|
|
29
|
+
...(opts.observedState !== undefined ? { observedState: opts.observedState } : {}),
|
|
30
|
+
...(opts.derivedState !== undefined ? { derivedState: opts.derivedState } : {})
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
async recordStateTransition(opts) {
|
|
34
|
+
return this.write({
|
|
35
|
+
...(opts.cycleId !== undefined ? { cycleId: opts.cycleId } : {}),
|
|
36
|
+
scope: opts.scope,
|
|
37
|
+
scopeId: opts.scopeId,
|
|
38
|
+
...(opts.repoId !== undefined ? { repoId: opts.repoId } : {}),
|
|
39
|
+
...(opts.workstreamId !== undefined ? { workstreamId: opts.workstreamId } : {}),
|
|
40
|
+
...(opts.issueNumber !== undefined ? { issueNumber: opts.issueNumber } : {}),
|
|
41
|
+
...(opts.prNumber !== undefined ? { prNumber: opts.prNumber } : {}),
|
|
42
|
+
category: "state_transition",
|
|
43
|
+
severity: "info",
|
|
44
|
+
summary: opts.summary,
|
|
45
|
+
stateTransition: {
|
|
46
|
+
from: opts.from,
|
|
47
|
+
to: opts.to,
|
|
48
|
+
...(opts.trigger !== undefined ? { trigger: opts.trigger } : {})
|
|
49
|
+
},
|
|
50
|
+
...(opts.observedState !== undefined ? { observedState: opts.observedState } : {}),
|
|
51
|
+
...(opts.derivedState !== undefined ? { derivedState: opts.derivedState } : {}),
|
|
52
|
+
...(opts.expectedNextAction !== undefined ? { expectedNextAction: opts.expectedNextAction } : {})
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async recordBlocked(opts) {
|
|
56
|
+
return this.write({
|
|
57
|
+
...(opts.cycleId !== undefined ? { cycleId: opts.cycleId } : {}),
|
|
58
|
+
scope: opts.scope,
|
|
59
|
+
scopeId: opts.scopeId,
|
|
60
|
+
...(opts.repoId !== undefined ? { repoId: opts.repoId } : {}),
|
|
61
|
+
...(opts.workstreamId !== undefined ? { workstreamId: opts.workstreamId } : {}),
|
|
62
|
+
...(opts.issueNumber !== undefined ? { issueNumber: opts.issueNumber } : {}),
|
|
63
|
+
...(opts.prNumber !== undefined ? { prNumber: opts.prNumber } : {}),
|
|
64
|
+
category: "blocked",
|
|
65
|
+
severity: "warning",
|
|
66
|
+
summary: opts.reason,
|
|
67
|
+
noActionReason: opts.reason,
|
|
68
|
+
...(opts.observedState !== undefined ? { observedState: opts.observedState } : {}),
|
|
69
|
+
...(opts.derivedState !== undefined ? { derivedState: opts.derivedState } : {})
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { appendFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
export class JournalWriter {
|
|
5
|
+
journalPath;
|
|
6
|
+
constructor(journalDir) {
|
|
7
|
+
this.journalPath = path.join(journalDir, "journal.jsonl");
|
|
8
|
+
}
|
|
9
|
+
async write(entry) {
|
|
10
|
+
const full = {
|
|
11
|
+
id: randomUUID(),
|
|
12
|
+
timestamp: new Date().toISOString(),
|
|
13
|
+
...entry
|
|
14
|
+
};
|
|
15
|
+
await mkdir(path.dirname(this.journalPath), { recursive: true });
|
|
16
|
+
await appendFile(this.journalPath, JSON.stringify(full) + "\n", "utf8");
|
|
17
|
+
return full;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import Smithery from "@smithery/api";
|
|
2
|
+
export class SmitheryConnectionToolsClient {
|
|
3
|
+
input;
|
|
4
|
+
constructor(input) {
|
|
5
|
+
this.input = input;
|
|
6
|
+
}
|
|
7
|
+
async listTools() {
|
|
8
|
+
const response = await this.input.client.connections.tools.list(this.input.connectionId, { namespace: this.input.namespace });
|
|
9
|
+
return response.tools.map((tool) => toBrokerTool(tool));
|
|
10
|
+
}
|
|
11
|
+
async getTool(name) {
|
|
12
|
+
try {
|
|
13
|
+
return toBrokerTool(await this.input.client.connections.tools.get(name, {
|
|
14
|
+
namespace: this.input.namespace,
|
|
15
|
+
connectionId: this.input.connectionId
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async callTool(name, rawArguments) {
|
|
23
|
+
try {
|
|
24
|
+
const body = normalizeToolArguments(rawArguments);
|
|
25
|
+
const result = await this.input.client.connections.tools.call(name, {
|
|
26
|
+
namespace: this.input.namespace,
|
|
27
|
+
connectionId: this.input.connectionId,
|
|
28
|
+
body
|
|
29
|
+
});
|
|
30
|
+
return {
|
|
31
|
+
ok: true,
|
|
32
|
+
tool: name,
|
|
33
|
+
result,
|
|
34
|
+
summary: `Smithery MCP tool "${name}" succeeded.`
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
return {
|
|
39
|
+
ok: false,
|
|
40
|
+
tool: name,
|
|
41
|
+
error: {
|
|
42
|
+
code: "smithery_tool_call_failed",
|
|
43
|
+
message: err instanceof Error ? err.message : String(err)
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function toBrokerTool(tool) {
|
|
50
|
+
return {
|
|
51
|
+
name: tool.name,
|
|
52
|
+
title: tool.title ?? tool.name,
|
|
53
|
+
description: tool.description ?? `Smithery MCP tool ${tool.name}`,
|
|
54
|
+
inputSchema: tool.inputSchema ?? { type: "object", properties: {} }
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export class ReadyMcpBridgeService {
|
|
58
|
+
handle;
|
|
59
|
+
client;
|
|
60
|
+
namespace;
|
|
61
|
+
cachedTools;
|
|
62
|
+
constructor(handle, client, allowedPublicToolNames) {
|
|
63
|
+
this.handle = handle;
|
|
64
|
+
this.client = client;
|
|
65
|
+
this.namespace = handle;
|
|
66
|
+
this.allowedLocalToolNames = allowedPublicToolNames
|
|
67
|
+
? new Set(allowedPublicToolNames
|
|
68
|
+
.filter((name) => name.startsWith(`${handle}.`))
|
|
69
|
+
.filter((name) => !name.endsWith(".*"))
|
|
70
|
+
.map((name) => name.slice(handle.length + 1)))
|
|
71
|
+
: undefined;
|
|
72
|
+
}
|
|
73
|
+
allowedLocalToolNames;
|
|
74
|
+
listTools() {
|
|
75
|
+
return (this.cachedTools ?? [])
|
|
76
|
+
.filter((tool) => this.isToolAllowed(tool.name))
|
|
77
|
+
.map((tool) => ({
|
|
78
|
+
...tool,
|
|
79
|
+
name: tool.name
|
|
80
|
+
}))
|
|
81
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
82
|
+
}
|
|
83
|
+
async initialize() {
|
|
84
|
+
try {
|
|
85
|
+
this.cachedTools = await this.client.listTools();
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
if (this.allowedLocalToolNames === undefined || this.client.getTool === undefined) {
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
const tools = [];
|
|
92
|
+
for (const toolName of [...this.allowedLocalToolNames].sort((left, right) => left.localeCompare(right))) {
|
|
93
|
+
const tool = await this.client.getTool(toolName);
|
|
94
|
+
if (tool) {
|
|
95
|
+
tools.push(tool);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (tools.length === 0) {
|
|
99
|
+
throw err;
|
|
100
|
+
}
|
|
101
|
+
this.cachedTools = tools;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async callTool(name, rawArguments) {
|
|
105
|
+
if (!this.isToolAllowed(name)) {
|
|
106
|
+
return {
|
|
107
|
+
ok: false,
|
|
108
|
+
tool: name,
|
|
109
|
+
error: {
|
|
110
|
+
code: "tool_not_found",
|
|
111
|
+
message: `MCP bridge tool "${this.handle}.${name}" is not enabled for this run`
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return await this.client.callTool(name, rawArguments);
|
|
116
|
+
}
|
|
117
|
+
isToolAllowed(localToolName) {
|
|
118
|
+
return (this.allowedLocalToolNames === undefined ||
|
|
119
|
+
this.allowedLocalToolNames.has(localToolName));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
export function createReadyMcpBridgeService(input) {
|
|
123
|
+
if (input.status.status !== "ready") {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
return new ReadyMcpBridgeService(input.status.handle, input.client, input.allowedPublicToolNames);
|
|
127
|
+
}
|
|
128
|
+
export function createReadyMcpBridgeServices(inputs) {
|
|
129
|
+
return [...inputs]
|
|
130
|
+
.sort((left, right) => left.status.handle.localeCompare(right.status.handle))
|
|
131
|
+
.map((input) => createReadyMcpBridgeService(input))
|
|
132
|
+
.filter((service) => service !== undefined);
|
|
133
|
+
}
|
|
134
|
+
export async function createSmitheryMcpBridgeServicesFromStatuses(statuses, runtimeConfig, client, allowedPublicToolNames) {
|
|
135
|
+
if (!runtimeConfig.apiKey && !client) {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
const sdkClient = client ?? new Smithery({ apiKey: runtimeConfig.apiKey });
|
|
139
|
+
const services = [];
|
|
140
|
+
for (const status of [...statuses].sort((left, right) => left.handle.localeCompare(right.handle))) {
|
|
141
|
+
if (status.status !== "ready") {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const namespace = status.connection?.namespace;
|
|
145
|
+
const connectionId = status.connection?.providerConnectionId;
|
|
146
|
+
if (!namespace || !connectionId) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const service = new ReadyMcpBridgeService(status.handle, new SmitheryConnectionToolsClient({ namespace, connectionId, client: sdkClient }), allowedPublicToolNames);
|
|
150
|
+
await service.initialize();
|
|
151
|
+
services.push(service);
|
|
152
|
+
}
|
|
153
|
+
return services;
|
|
154
|
+
}
|
|
155
|
+
export function projectReadyMcpBridge(service, status) {
|
|
156
|
+
return {
|
|
157
|
+
handle: status.handle,
|
|
158
|
+
tools: service.listTools().map((tool) => tool.name)
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function normalizeToolArguments(rawArguments) {
|
|
162
|
+
if (rawArguments && typeof rawArguments === "object" && !Array.isArray(rawArguments)) {
|
|
163
|
+
return rawArguments;
|
|
164
|
+
}
|
|
165
|
+
return {};
|
|
166
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { SmitheryMcpResolver } from "./smithery.js";
|
|
2
|
+
export { createSmitheryMcpResolverFromEnv } from "./smithery.js";
|
|
3
|
+
export { SmitherySdkClient } from "./smithery-client.js";
|
|
4
|
+
export { ReadyMcpBridgeService, createReadyMcpBridgeService, createReadyMcpBridgeServices, createSmitheryMcpBridgeServicesFromStatuses, SmitheryConnectionToolsClient, projectReadyMcpBridge } from "./bridge.js";
|
|
5
|
+
export { reconcileMcpStatus } from "./reconcile.js";
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
export function reconcileMcpStatus(dependencies, previous, resolverResults, now) {
|
|
2
|
+
const previousByHandle = new Map(previous.map((r) => [r.handle, r]));
|
|
3
|
+
const currentHandles = new Set(dependencies.map((d) => d.handle));
|
|
4
|
+
const resultByHandle = new Map(resolverResults.map((r) => [r.handle, r]));
|
|
5
|
+
const added = [];
|
|
6
|
+
const updated = [];
|
|
7
|
+
const unchanged = [];
|
|
8
|
+
const removed = [];
|
|
9
|
+
// Drop records for handles no longer in dependencies.
|
|
10
|
+
for (const prev of previous) {
|
|
11
|
+
if (!currentHandles.has(prev.handle)) {
|
|
12
|
+
removed.push(prev.handle);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const records = [];
|
|
16
|
+
for (const dep of dependencies) {
|
|
17
|
+
const resolved = resultByHandle.get(dep.handle);
|
|
18
|
+
const prev = previousByHandle.get(dep.handle);
|
|
19
|
+
if (!resolved) {
|
|
20
|
+
// No resolver result for this handle — carry forward previous if any.
|
|
21
|
+
if (prev) {
|
|
22
|
+
records.push(prev);
|
|
23
|
+
unchanged.push(dep.handle);
|
|
24
|
+
}
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const next = resolvedMcpToStatusRecord(resolved, now);
|
|
28
|
+
if (!prev) {
|
|
29
|
+
added.push(dep.handle);
|
|
30
|
+
}
|
|
31
|
+
else if (prev.status === next.status &&
|
|
32
|
+
prev.actionRequired?.reason === next.actionRequired?.reason &&
|
|
33
|
+
prev.connection?.providerConnectionId === next.connection?.providerConnectionId &&
|
|
34
|
+
prev.connection?.readinessProof === next.connection?.readinessProof) {
|
|
35
|
+
// Status, action_required reason, and connection identity/proof are unchanged — still update lastCheckedAt and message.
|
|
36
|
+
unchanged.push(dep.handle);
|
|
37
|
+
records.push({ ...next });
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
updated.push(dep.handle);
|
|
42
|
+
}
|
|
43
|
+
records.push(next);
|
|
44
|
+
}
|
|
45
|
+
// Sort deterministically by handle.
|
|
46
|
+
records.sort((a, b) => a.handle.localeCompare(b.handle));
|
|
47
|
+
return { records, added, updated, unchanged, removed };
|
|
48
|
+
}
|
|
49
|
+
function resolvedMcpToStatusRecord(resolved, now) {
|
|
50
|
+
const record = {
|
|
51
|
+
handle: resolved.handle,
|
|
52
|
+
providerId: resolved.provider,
|
|
53
|
+
providerMcpId: resolved.providerMcpId,
|
|
54
|
+
status: resolved.status,
|
|
55
|
+
lastCheckedAt: now,
|
|
56
|
+
};
|
|
57
|
+
if (resolved.message) {
|
|
58
|
+
record.message = resolved.message;
|
|
59
|
+
}
|
|
60
|
+
if (resolved.actionRequired) {
|
|
61
|
+
// Only persist the reason, user-visible message, safe fields list.
|
|
62
|
+
// Do NOT persist URLs unless they are explicitly safe (non-secret, non-short-lived).
|
|
63
|
+
// For V1, omit URL persistence to stay conservative.
|
|
64
|
+
record.actionRequired = {
|
|
65
|
+
reason: resolved.actionRequired.reason,
|
|
66
|
+
...(resolved.actionRequired.message
|
|
67
|
+
? { message: resolved.actionRequired.message }
|
|
68
|
+
: {}),
|
|
69
|
+
...(resolved.actionRequired.fields?.length
|
|
70
|
+
? { fields: [...resolved.actionRequired.fields] }
|
|
71
|
+
: {}),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (resolved.connection) {
|
|
75
|
+
// Only persist non-secret connection metadata.
|
|
76
|
+
// mcpUrl is intentionally omitted: Smithery bundleUrl/deploymentUrl may embed
|
|
77
|
+
// short-lived auth tokens. transport (e.g. "http", "streamable-http") is safe.
|
|
78
|
+
record.connection = {
|
|
79
|
+
...(resolved.connection.localConnectionIdentity
|
|
80
|
+
? { localConnectionIdentity: resolved.connection.localConnectionIdentity }
|
|
81
|
+
: {}),
|
|
82
|
+
...(resolved.connection.providerConnectionId
|
|
83
|
+
? { providerConnectionId: resolved.connection.providerConnectionId }
|
|
84
|
+
: {}),
|
|
85
|
+
...(resolved.connection.namespace
|
|
86
|
+
? { namespace: resolved.connection.namespace }
|
|
87
|
+
: {}),
|
|
88
|
+
...(resolved.connection.transport
|
|
89
|
+
? { transport: resolved.connection.transport }
|
|
90
|
+
: {}),
|
|
91
|
+
...(resolved.status === "ready" && resolved.connection.readiness === "connected"
|
|
92
|
+
? { readinessProof: "connected", readinessCheckedAt: now }
|
|
93
|
+
: {}),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
if (resolved.discovered) {
|
|
97
|
+
// Store discovery summary (counts) rather than full tool lists for now.
|
|
98
|
+
// Full projection is out of scope for this issue.
|
|
99
|
+
record.discovered = {
|
|
100
|
+
...(resolved.discovered.name ? { name: resolved.discovered.name } : {}),
|
|
101
|
+
...(resolved.discovered.tools?.length !== undefined
|
|
102
|
+
? { toolCount: resolved.discovered.tools?.length ?? 0 }
|
|
103
|
+
: {}),
|
|
104
|
+
...(resolved.discovered.resources?.length !== undefined
|
|
105
|
+
? { resourceCount: resolved.discovered.resources?.length ?? 0 }
|
|
106
|
+
: {}),
|
|
107
|
+
...(resolved.discovered.prompts?.length !== undefined
|
|
108
|
+
? { promptCount: resolved.discovered.prompts?.length ?? 0 }
|
|
109
|
+
: {}),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return record;
|
|
113
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import Smithery from "@smithery/api";
|
|
2
|
+
function isSmitherySdkClientShape(candidate) {
|
|
3
|
+
if (!candidate || typeof candidate !== "object") {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
const maybe = candidate;
|
|
7
|
+
return typeof maybe.servers?.get === "function" && typeof maybe.connections?.get === "function" && typeof maybe.connections?.set === "function";
|
|
8
|
+
}
|
|
9
|
+
export class SmitherySdkClient {
|
|
10
|
+
runtimeConfig;
|
|
11
|
+
fetchFn;
|
|
12
|
+
sdkClient;
|
|
13
|
+
constructor(runtimeConfig, clientOrFetch, sdkClient) {
|
|
14
|
+
this.runtimeConfig = runtimeConfig;
|
|
15
|
+
this.fetchFn = typeof clientOrFetch === "function" ? clientOrFetch : fetch;
|
|
16
|
+
if (isSmitherySdkClientShape(clientOrFetch)) {
|
|
17
|
+
this.sdkClient = clientOrFetch;
|
|
18
|
+
}
|
|
19
|
+
else if (sdkClient) {
|
|
20
|
+
this.sdkClient = sdkClient;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async getServerByExactId(smitheryId) {
|
|
24
|
+
if (!this.runtimeConfig.apiKey) {
|
|
25
|
+
return {
|
|
26
|
+
status: "error",
|
|
27
|
+
message: "Missing Smithery API key.",
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const server = await this.getClient().servers.get(smitheryId);
|
|
32
|
+
// Check for required configuration
|
|
33
|
+
const configProperties = new Set();
|
|
34
|
+
if (Array.isArray(server.connections)) {
|
|
35
|
+
for (const conn of server.connections) {
|
|
36
|
+
if (conn.configSchema?.properties) {
|
|
37
|
+
for (const key of Object.keys(conn.configSchema.properties)) {
|
|
38
|
+
configProperties.add(key);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const actionRequired = configProperties.size > 0
|
|
44
|
+
? {
|
|
45
|
+
reason: "config_required",
|
|
46
|
+
message: `Configuration required for Smithery server "${smitheryId}".`,
|
|
47
|
+
fields: Array.from(configProperties),
|
|
48
|
+
}
|
|
49
|
+
: undefined;
|
|
50
|
+
// Map connection info if available. We prefer hosted HTTP connections.
|
|
51
|
+
const httpConn = server.connections?.find((c) => c.type === "http");
|
|
52
|
+
const connection = httpConn
|
|
53
|
+
? {
|
|
54
|
+
transport: "http",
|
|
55
|
+
mcpUrl: httpConn.deploymentUrl ||
|
|
56
|
+
httpConn.bundleUrl ||
|
|
57
|
+
server.deploymentUrl ||
|
|
58
|
+
undefined,
|
|
59
|
+
readiness: "unknown",
|
|
60
|
+
}
|
|
61
|
+
: undefined;
|
|
62
|
+
return {
|
|
63
|
+
status: "found",
|
|
64
|
+
server: {
|
|
65
|
+
name: server.displayName || server.qualifiedName,
|
|
66
|
+
tools: server.tools ?? undefined,
|
|
67
|
+
resources: server.resources ?? undefined,
|
|
68
|
+
prompts: server.prompts ?? undefined,
|
|
69
|
+
},
|
|
70
|
+
actionRequired,
|
|
71
|
+
connection,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
if (err.status === 404) {
|
|
76
|
+
return {
|
|
77
|
+
status: "not_found",
|
|
78
|
+
message: `Smithery server "${smitheryId}" not found.`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (err.status === 401 || err.status === 403) {
|
|
82
|
+
return {
|
|
83
|
+
status: "error",
|
|
84
|
+
message: `Smithery API key is missing, invalid, or unauthorized (HTTP ${err.status}). Check the SMITHERY_API_KEY runtime configuration.`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
status: "error",
|
|
89
|
+
message: err.message || String(err),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async getConnection(namespace, connectionId) {
|
|
94
|
+
if (!this.runtimeConfig.apiKey) {
|
|
95
|
+
return { status: "error", message: "Missing Smithery API key." };
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const connection = await this.getClient().connections.get(connectionId, { namespace });
|
|
99
|
+
return this.mapConnectionData(connection);
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
if (err.status === 404) {
|
|
103
|
+
return {
|
|
104
|
+
status: "not_found",
|
|
105
|
+
message: `Connection "${connectionId}" not found in namespace "${namespace}".`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (err.status === 401 || err.status === 403) {
|
|
109
|
+
return {
|
|
110
|
+
status: "error",
|
|
111
|
+
message: `Smithery API key is missing, invalid, or unauthorized (HTTP ${err.status}). Check the SMITHERY_API_KEY runtime configuration.`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return { status: "error", message: err.message || String(err) };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async createOrUpdateConnection(input) {
|
|
118
|
+
if (!this.runtimeConfig.apiKey) {
|
|
119
|
+
return { status: "error", message: "Missing Smithery API key." };
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const connection = await this.getClient().connections.set(input.connectionId, {
|
|
123
|
+
namespace: input.namespace,
|
|
124
|
+
server: input.smitheryId,
|
|
125
|
+
name: input.handle,
|
|
126
|
+
...(input.transport ? { transport: input.transport } : {}),
|
|
127
|
+
metadata: {
|
|
128
|
+
backburner: "backburner",
|
|
129
|
+
backburnerHandle: input.handle,
|
|
130
|
+
providerMcpId: input.smitheryId,
|
|
131
|
+
localConnectionIdentity: input.connectionId,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
return this.mapConnectionData(connection);
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
if (err.status === 401 || err.status === 403) {
|
|
138
|
+
return {
|
|
139
|
+
status: "error",
|
|
140
|
+
message: `Smithery API key is missing, invalid, or unauthorized (HTTP ${err.status}). Check the SMITHERY_API_KEY runtime configuration.`,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return { status: "error", message: err.message || String(err) };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
mapConnectionData(data) {
|
|
147
|
+
const state = data.status?.state;
|
|
148
|
+
let status = "error";
|
|
149
|
+
if (state === "connected")
|
|
150
|
+
status = "connected";
|
|
151
|
+
else if (state === "auth_required")
|
|
152
|
+
status = "auth_required";
|
|
153
|
+
else if (state === "input_required")
|
|
154
|
+
status = "input_required";
|
|
155
|
+
else if (state === "disconnected")
|
|
156
|
+
status = "disconnected";
|
|
157
|
+
const rawMetadata = data.metadata;
|
|
158
|
+
const metadata = rawMetadata && typeof rawMetadata === "object" && !Array.isArray(rawMetadata)
|
|
159
|
+
? rawMetadata
|
|
160
|
+
: undefined;
|
|
161
|
+
return {
|
|
162
|
+
status,
|
|
163
|
+
message: data.status?.message,
|
|
164
|
+
connectionId: data.connectionId,
|
|
165
|
+
namespace: data.namespace,
|
|
166
|
+
transport: data.transport ?? undefined,
|
|
167
|
+
mcpUrl: data.mcpUrl || undefined,
|
|
168
|
+
setupUrl: data.status?.setupUrl || data.status?.url,
|
|
169
|
+
fields: data.status?.fields,
|
|
170
|
+
...(metadata ? { metadata } : {}),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
getClient() {
|
|
174
|
+
if (this.sdkClient) {
|
|
175
|
+
return this.sdkClient;
|
|
176
|
+
}
|
|
177
|
+
if (!this.runtimeConfig.apiKey) {
|
|
178
|
+
throw new Error("Missing Smithery API key.");
|
|
179
|
+
}
|
|
180
|
+
this.sdkClient = new Smithery({
|
|
181
|
+
apiKey: this.runtimeConfig.apiKey,
|
|
182
|
+
fetch: this.fetchFn,
|
|
183
|
+
});
|
|
184
|
+
return this.sdkClient;
|
|
185
|
+
}
|
|
186
|
+
}
|