@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 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Task } from "../tasks/task.js";
|
|
2
|
+
export class GitHubWaveAssessmentPublisher {
|
|
3
|
+
gitHubGateway;
|
|
4
|
+
logger;
|
|
5
|
+
reposById;
|
|
6
|
+
constructor(gitHubGateway, repos, logger) {
|
|
7
|
+
this.gitHubGateway = gitHubGateway;
|
|
8
|
+
this.logger = logger;
|
|
9
|
+
this.reposById = new Map(repos.map((repo) => [repo.id, repo]));
|
|
10
|
+
}
|
|
11
|
+
async publishWaveAssessment(task, execution) {
|
|
12
|
+
if ((task.kind !== "wave_plan_assessment" && task.kind !== "wave_plan_replanning") ||
|
|
13
|
+
execution.taskStatus !== "completed") {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
const assessment = execution.result?.waveAssessment;
|
|
17
|
+
if (!assessment) {
|
|
18
|
+
this.logger?.warn(`Completed ${task.kind} task ${task.id} did not return a structured assessment result.`);
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
const repo = this.reposById.get(task.repoId);
|
|
22
|
+
const pullRequestNumber = Task.from(task).owningPullRequestNumber();
|
|
23
|
+
if (!repo || pullRequestNumber === undefined) {
|
|
24
|
+
this.logger?.warn(`Could not resolve repo or parent PR number for wave assessment task ${task.id}.`);
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
const comment = await this.gitHubGateway.postIssueComment({
|
|
28
|
+
owner: repo.owner,
|
|
29
|
+
name: repo.name,
|
|
30
|
+
issueNumber: pullRequestNumber,
|
|
31
|
+
body: renderWaveAssessmentComment(assessment)
|
|
32
|
+
});
|
|
33
|
+
return String(comment.id);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function renderWaveAssessmentComment(assessment) {
|
|
37
|
+
const { identity } = assessment.checkpoint;
|
|
38
|
+
const lines = [
|
|
39
|
+
"🤖 **Wave Plan Assessment Report**",
|
|
40
|
+
"",
|
|
41
|
+
`- **Wave**: ${identity.waveId}`,
|
|
42
|
+
`- **Decision**: ${assessment.decision}`,
|
|
43
|
+
`- **Assessed Parent Head SHA**: \`${identity.parentHeadSha}\``,
|
|
44
|
+
`- **Plan Revision**: \`${identity.planRevision}\``,
|
|
45
|
+
`- **Assessment Session**: \`${assessment.sessionId}\``,
|
|
46
|
+
"",
|
|
47
|
+
"## Summary",
|
|
48
|
+
"",
|
|
49
|
+
assessment.summary,
|
|
50
|
+
"",
|
|
51
|
+
"## Findings",
|
|
52
|
+
""
|
|
53
|
+
];
|
|
54
|
+
if (assessment.findings.length === 0) {
|
|
55
|
+
lines.push("- None");
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
for (const finding of assessment.findings) {
|
|
59
|
+
const affected = finding.affectedPlanNodeIds.length > 0
|
|
60
|
+
? finding.affectedPlanNodeIds.join(", ")
|
|
61
|
+
: "none";
|
|
62
|
+
lines.push(`- **[${finding.category}]** ${finding.summary} (Affected plan nodes: ${affected})`);
|
|
63
|
+
if (finding.details.trim()) {
|
|
64
|
+
lines.push(` ${finding.details.replace(/\s+/g, " ").trim()}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return lines.join("\n").trim();
|
|
69
|
+
}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { extractRetryCommentId } from "../tasks/retry.js";
|
|
2
|
+
import { extractMcpSkipCommand } from "../tasks/mcp-skip.js";
|
|
3
|
+
import { hasGeneratedPlanBreakdown, isParentSyncNeededBeforeNextWave } from "../workstreams/packet-plan.js";
|
|
4
|
+
import { collectPacketPullRequestCommentProjections, collectPacketPullRequestProjections } from "../workstreams/packet-projections.js";
|
|
5
|
+
export function deriveAttentionRecords(input) {
|
|
6
|
+
const reposById = new Map(input.repos.map((repo) => [repo.id, repo]));
|
|
7
|
+
const currentIssuesById = new Map(input.currentIssues.map((issue) => [issue.id, issue]));
|
|
8
|
+
const currentPrsById = new Map(input.currentPullRequests.map((pr) => [pr.id, pr]));
|
|
9
|
+
const previousIssuesById = new Map(input.previousState.issues.map((issue) => [issue.id, issue]));
|
|
10
|
+
const previousPrsById = new Map(input.previousState.prs.map((pr) => [pr.id, pr]));
|
|
11
|
+
const previousCommentsById = new Map(input.previousState.comments.map((comment) => [comment.id, comment]));
|
|
12
|
+
const previousAttentionByWorkstreamId = new Map(input.previousState.attention.map((record) => [record.workstreamId, record]));
|
|
13
|
+
const issueWorkstreamByKey = new Map();
|
|
14
|
+
const prWorkstreamByKey = new Map();
|
|
15
|
+
const handledReviewHeadShaByPrId = buildHandledReviewHeadShaByPrId(input.previousState, input.currentPullRequests, input.tasks);
|
|
16
|
+
for (const workstream of input.workstreams) {
|
|
17
|
+
if (workstream.origin.type === "issue" && workstream.linkedIssueNumber !== undefined) {
|
|
18
|
+
issueWorkstreamByKey.set(buildIssueKey(workstream.repoKey, workstream.linkedIssueNumber), workstream);
|
|
19
|
+
}
|
|
20
|
+
for (const pr of workstream.linkedPrs) {
|
|
21
|
+
prWorkstreamByKey.set(buildPrKey(workstream.repoKey, pr.number), workstream);
|
|
22
|
+
}
|
|
23
|
+
if (workstream.origin.type === "pr") {
|
|
24
|
+
prWorkstreamByKey.set(buildPrKey(workstream.repoKey, workstream.origin.number), workstream);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const worktrees = input.worktrees ?? input.previousState.worktrees;
|
|
28
|
+
const packetPullRequestProjections = collectPacketPullRequestProjections({
|
|
29
|
+
pullRequests: input.currentPullRequests,
|
|
30
|
+
workstreams: input.workstreams,
|
|
31
|
+
worktrees
|
|
32
|
+
});
|
|
33
|
+
const packetCommentProjectionByCommentId = new Map(collectPacketPullRequestCommentProjections({
|
|
34
|
+
comments: input.currentComments,
|
|
35
|
+
packetPullRequestProjections
|
|
36
|
+
}).map((projection) => [projection.comment.id, projection]));
|
|
37
|
+
const attentionByWorkstreamId = new Map();
|
|
38
|
+
for (const workstream of input.workstreams) {
|
|
39
|
+
const currentIssue = workstream.origin.type === "issue"
|
|
40
|
+
? currentIssuesById.get(buildEntityId("issue", workstream.repoKey, workstream.origin.number))
|
|
41
|
+
: undefined;
|
|
42
|
+
const previousAttention = previousAttentionByWorkstreamId.get(workstream.id);
|
|
43
|
+
const accumulator = getAccumulator(attentionByWorkstreamId, workstream.id);
|
|
44
|
+
if (currentIssue) {
|
|
45
|
+
accumulator.issueNumber = currentIssue.number;
|
|
46
|
+
if (!previousIssuesById.has(currentIssue.id)) {
|
|
47
|
+
accumulator.reasons.add("new_issue");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else if (workstream.linkedIssueNumber !== undefined) {
|
|
51
|
+
accumulator.issueNumber = workstream.linkedIssueNumber;
|
|
52
|
+
}
|
|
53
|
+
const currentLinkedPrs = resolveCurrentLinkedPullRequests(workstream, currentPrsById);
|
|
54
|
+
const currentOpenLinkedPrs = currentLinkedPrs.filter((pr) => pr.state === "open");
|
|
55
|
+
const repo = currentIssue?.repoId !== undefined
|
|
56
|
+
? reposById.get(currentIssue.repoId)
|
|
57
|
+
: currentLinkedPrs[0]
|
|
58
|
+
? reposById.get(currentLinkedPrs[0].repoId)
|
|
59
|
+
: undefined;
|
|
60
|
+
for (const pr of currentLinkedPrs) {
|
|
61
|
+
accumulator.pullRequestNumbers.add(pr.number);
|
|
62
|
+
if (!previousPrsById.has(pr.id)) {
|
|
63
|
+
accumulator.reasons.add("new_pr");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (workstream.origin.type === "pr" && currentLinkedPrs.length > 0) {
|
|
67
|
+
accumulator.reasons.add("workstream_unlinked_pr");
|
|
68
|
+
}
|
|
69
|
+
if (repo) {
|
|
70
|
+
for (const pr of currentOpenLinkedPrs) {
|
|
71
|
+
if (!pr.labels.includes(repo.labels.agentReview)) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
accumulator.reasons.add("pr_review_requested");
|
|
75
|
+
accumulator.currentPullRequestHeadSha = pr.headSha;
|
|
76
|
+
const handledHeadSha = handledReviewHeadShaByPrId.get(pr.id) ?? previousAttention?.lastHandledReviewHeadSha;
|
|
77
|
+
if (handledHeadSha) {
|
|
78
|
+
accumulator.lastHandledReviewHeadSha = handledHeadSha;
|
|
79
|
+
if (handledHeadSha !== pr.headSha) {
|
|
80
|
+
accumulator.reasons.add("pr_head_changed_since_last_review");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
accumulator.gaps.add("Review handling is inferred from persisted state; external or unlabeled reviews are not tracked yet.");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (currentOpenLinkedPrs.some((pr) => pr.labels.includes(repo.labels.agentPlanApproved))) {
|
|
88
|
+
accumulator.reasons.add("plan_approved");
|
|
89
|
+
}
|
|
90
|
+
if (currentIssue?.labels.includes(repo.labels.agentProductApproved)) {
|
|
91
|
+
accumulator.reasons.add("product_approval_observed");
|
|
92
|
+
}
|
|
93
|
+
const planBreakdownAlreadyProduced = hasGeneratedPlanBreakdown(workstream);
|
|
94
|
+
if (!planBreakdownAlreadyProduced && currentIssue?.labels.includes(repo.labels.planBreakdownNeeded)) {
|
|
95
|
+
accumulator.reasons.add("plan_breakdown_requested");
|
|
96
|
+
}
|
|
97
|
+
if (!planBreakdownAlreadyProduced &&
|
|
98
|
+
currentOpenLinkedPrs.some((pr) => pr.labels.includes(repo.labels.planBreakdownNeeded))) {
|
|
99
|
+
accumulator.reasons.add("plan_breakdown_requested");
|
|
100
|
+
}
|
|
101
|
+
if (currentOpenLinkedPrs.some((pr) => pr.labels.includes(repo.labels.prepareForMerge))) {
|
|
102
|
+
accumulator.reasons.add("prepare_for_merge_requested");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (workstream.origin.type === "issue" &&
|
|
106
|
+
workstream.packetPlan &&
|
|
107
|
+
isParentSyncNeededBeforeNextWave(workstream.packetPlan)) {
|
|
108
|
+
accumulator.reasons.add("packet_wave_complete_sync_needed");
|
|
109
|
+
}
|
|
110
|
+
if (previousAttention?.issueNumber !== undefined && accumulator.issueNumber === undefined) {
|
|
111
|
+
accumulator.issueNumber = previousAttention.issueNumber;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
for (const comment of [...input.currentComments].sort(compareComments)) {
|
|
115
|
+
if (isAgentAuthoredComment(comment.body)) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const previousComment = previousCommentsById.get(comment.id);
|
|
119
|
+
const isNew = previousComment === undefined;
|
|
120
|
+
const isEdited = previousComment !== undefined &&
|
|
121
|
+
(previousComment.updatedAt !== comment.updatedAt || previousComment.body !== comment.body);
|
|
122
|
+
if (!isNew && !isEdited) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
let workstream = comment.parentType === "issue"
|
|
126
|
+
? issueWorkstreamByKey.get(buildIssueKey(comment.repoKey, comment.parentNumber))
|
|
127
|
+
: prWorkstreamByKey.get(buildPrKey(comment.repoKey, comment.parentNumber));
|
|
128
|
+
if (!workstream && comment.parentType === "pull_request") {
|
|
129
|
+
const packetProjection = packetCommentProjectionByCommentId.get(comment.id);
|
|
130
|
+
if (packetProjection) {
|
|
131
|
+
workstream = packetProjection.packetProjection.parentWorkstream;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (!workstream) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const accumulator = getAccumulator(attentionByWorkstreamId, workstream.id);
|
|
138
|
+
accumulator.commentIds.add(comment.id);
|
|
139
|
+
if (comment.parentType === "issue") {
|
|
140
|
+
accumulator.issueNumber ??= comment.parentNumber;
|
|
141
|
+
accumulator.reasons.add(isNew ? "issue_comment_added" : "issue_comment_edited");
|
|
142
|
+
const issueRetryCommentId = extractRetryCommentId(comment.body);
|
|
143
|
+
if (issueRetryCommentId !== undefined) {
|
|
144
|
+
accumulator.reasons.add("retry_requested");
|
|
145
|
+
accumulator.retryTargetCommentId ??= issueRetryCommentId;
|
|
146
|
+
}
|
|
147
|
+
if (isCancelRequest(comment.body)) {
|
|
148
|
+
accumulator.reasons.add("cancel_requested");
|
|
149
|
+
}
|
|
150
|
+
const mcpSkipCommand = extractMcpSkipCommand(comment.body);
|
|
151
|
+
if (mcpSkipCommand !== undefined) {
|
|
152
|
+
const matchingBlocker = findMatchingMcpBlocker(workstream, mcpSkipCommand.taskId, mcpSkipCommand.mcpHandle);
|
|
153
|
+
const isStale = matchingBlocker !== undefined && comment.createdAt < matchingBlocker.createdAt;
|
|
154
|
+
if (!isStale) {
|
|
155
|
+
accumulator.reasons.add("mcp_skip_requested");
|
|
156
|
+
accumulator.mcpSkipTargetTaskId ??= mcpSkipCommand.taskId;
|
|
157
|
+
accumulator.mcpSkipMcpHandle ??= mcpSkipCommand.mcpHandle;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
accumulator.pullRequestNumbers.add(comment.parentNumber);
|
|
163
|
+
accumulator.reasons.add(isNew ? "pr_comment_added" : "pr_comment_edited");
|
|
164
|
+
const retryCommentId = extractRetryCommentId(comment.body);
|
|
165
|
+
if (retryCommentId !== undefined) {
|
|
166
|
+
accumulator.reasons.add("retry_requested");
|
|
167
|
+
accumulator.retryTargetCommentId ??= retryCommentId;
|
|
168
|
+
}
|
|
169
|
+
if (isCancelRequest(comment.body)) {
|
|
170
|
+
accumulator.reasons.add("cancel_requested");
|
|
171
|
+
}
|
|
172
|
+
const mcpSkipCommand = extractMcpSkipCommand(comment.body);
|
|
173
|
+
if (mcpSkipCommand !== undefined) {
|
|
174
|
+
const matchingBlocker = findMatchingMcpBlocker(workstream, mcpSkipCommand.taskId, mcpSkipCommand.mcpHandle);
|
|
175
|
+
const isStale = matchingBlocker !== undefined && comment.createdAt < matchingBlocker.createdAt;
|
|
176
|
+
if (!isStale) {
|
|
177
|
+
accumulator.reasons.add("mcp_skip_requested");
|
|
178
|
+
accumulator.mcpSkipTargetTaskId ??= mcpSkipCommand.taskId;
|
|
179
|
+
accumulator.mcpSkipMcpHandle ??= mcpSkipCommand.mcpHandle;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return [...input.workstreams]
|
|
184
|
+
.sort((left, right) => left.id.localeCompare(right.id))
|
|
185
|
+
.map((workstream) => {
|
|
186
|
+
const accumulator = attentionByWorkstreamId.get(workstream.id) ?? {
|
|
187
|
+
reasons: new Set(),
|
|
188
|
+
pullRequestNumbers: new Set(),
|
|
189
|
+
commentIds: new Set(),
|
|
190
|
+
gaps: new Set()
|
|
191
|
+
};
|
|
192
|
+
const previous = previousAttentionByWorkstreamId.get(workstream.id);
|
|
193
|
+
const reasons = [...accumulator.reasons].sort((left, right) => left.localeCompare(right));
|
|
194
|
+
const needsAction = workstream.status === "open" &&
|
|
195
|
+
evaluateNeedsAction(reasons, accumulator.lastHandledReviewHeadSha, accumulator.currentPullRequestHeadSha);
|
|
196
|
+
return {
|
|
197
|
+
workstreamId: workstream.id,
|
|
198
|
+
repoKey: workstream.repoKey,
|
|
199
|
+
needsAction,
|
|
200
|
+
reasons,
|
|
201
|
+
...(accumulator.issueNumber !== undefined ? { issueNumber: accumulator.issueNumber } : {}),
|
|
202
|
+
...(accumulator.pullRequestNumbers.size > 0
|
|
203
|
+
? {
|
|
204
|
+
pullRequestNumbers: [...accumulator.pullRequestNumbers].sort((left, right) => left - right)
|
|
205
|
+
}
|
|
206
|
+
: {}),
|
|
207
|
+
...(accumulator.commentIds.size > 0
|
|
208
|
+
? { commentIds: [...accumulator.commentIds].sort((left, right) => left.localeCompare(right)) }
|
|
209
|
+
: {}),
|
|
210
|
+
...(accumulator.retryTargetCommentId !== undefined
|
|
211
|
+
? { retryTargetCommentId: accumulator.retryTargetCommentId }
|
|
212
|
+
: {}),
|
|
213
|
+
...(accumulator.mcpSkipTargetTaskId !== undefined
|
|
214
|
+
? { mcpSkipTargetTaskId: accumulator.mcpSkipTargetTaskId }
|
|
215
|
+
: {}),
|
|
216
|
+
...(accumulator.mcpSkipMcpHandle !== undefined
|
|
217
|
+
? { mcpSkipMcpHandle: accumulator.mcpSkipMcpHandle }
|
|
218
|
+
: {}),
|
|
219
|
+
lastObservedAt: input.now,
|
|
220
|
+
...(previous?.lastHandledAt ? { lastHandledAt: previous.lastHandledAt } : {}),
|
|
221
|
+
...(accumulator.currentPullRequestHeadSha
|
|
222
|
+
? { currentPullRequestHeadSha: accumulator.currentPullRequestHeadSha }
|
|
223
|
+
: {}),
|
|
224
|
+
...(accumulator.lastHandledReviewHeadSha
|
|
225
|
+
? { lastHandledReviewHeadSha: accumulator.lastHandledReviewHeadSha }
|
|
226
|
+
: {}),
|
|
227
|
+
...(accumulator.gaps.size > 0
|
|
228
|
+
? { gaps: [...accumulator.gaps].sort((left, right) => left.localeCompare(right)) }
|
|
229
|
+
: {})
|
|
230
|
+
};
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
function buildHandledReviewHeadShaByPrId(previousState, currentPullRequests, tasks) {
|
|
234
|
+
const handledByPrId = new Map();
|
|
235
|
+
const previousPrsById = new Map(previousState.prs.map((pr) => [pr.id, pr]));
|
|
236
|
+
const currentPrsById = new Map(currentPullRequests.map((pr) => [pr.id, pr]));
|
|
237
|
+
for (const task of previousState.tasks) {
|
|
238
|
+
if (task.kind !== "review_pr" || task.status !== "completed") {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
const previousPr = previousPrsById.get(task.sourceId);
|
|
242
|
+
if (previousPr) {
|
|
243
|
+
handledByPrId.set(task.sourceId, previousPr.headSha);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
for (const task of tasks) {
|
|
247
|
+
if (task.kind !== "review_pr" || task.status !== "completed") {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const wasAlreadyCompleted = previousState.tasks.some((t) => t.id === task.id && t.status === "completed");
|
|
251
|
+
if (wasAlreadyCompleted) {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
const currentPr = currentPrsById.get(task.sourceId);
|
|
255
|
+
if (currentPr) {
|
|
256
|
+
handledByPrId.set(task.sourceId, currentPr.headSha);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return handledByPrId;
|
|
260
|
+
}
|
|
261
|
+
function resolveCurrentLinkedPullRequests(workstream, currentPrsById) {
|
|
262
|
+
const prIds = new Set();
|
|
263
|
+
if (workstream.origin.type === "pr") {
|
|
264
|
+
prIds.add(buildEntityId("pull_request", workstream.repoKey, workstream.origin.number));
|
|
265
|
+
}
|
|
266
|
+
for (const linkedPr of workstream.linkedPrs) {
|
|
267
|
+
prIds.add(buildEntityId("pull_request", workstream.repoKey, linkedPr.number));
|
|
268
|
+
}
|
|
269
|
+
return [...prIds]
|
|
270
|
+
.map((id) => currentPrsById.get(id))
|
|
271
|
+
.filter((pr) => pr !== undefined)
|
|
272
|
+
.sort((left, right) => left.number - right.number);
|
|
273
|
+
}
|
|
274
|
+
function getAccumulator(attentionByWorkstreamId, workstreamId) {
|
|
275
|
+
const existing = attentionByWorkstreamId.get(workstreamId);
|
|
276
|
+
if (existing) {
|
|
277
|
+
return existing;
|
|
278
|
+
}
|
|
279
|
+
const created = {
|
|
280
|
+
reasons: new Set(),
|
|
281
|
+
pullRequestNumbers: new Set(),
|
|
282
|
+
commentIds: new Set(),
|
|
283
|
+
gaps: new Set()
|
|
284
|
+
};
|
|
285
|
+
attentionByWorkstreamId.set(workstreamId, created);
|
|
286
|
+
return created;
|
|
287
|
+
}
|
|
288
|
+
function evaluateNeedsAction(reasons, lastHandledReviewHeadSha, currentPullRequestHeadSha) {
|
|
289
|
+
return reasons.some((reason) => reason === "new_issue" ||
|
|
290
|
+
reason === "issue_comment_added" ||
|
|
291
|
+
reason === "issue_comment_edited" ||
|
|
292
|
+
reason === "new_pr" ||
|
|
293
|
+
reason === "pr_comment_added" ||
|
|
294
|
+
reason === "pr_comment_edited" ||
|
|
295
|
+
reason === "pr_head_changed_since_last_review" ||
|
|
296
|
+
reason === "product_approval_observed" ||
|
|
297
|
+
reason === "plan_approved" ||
|
|
298
|
+
reason === "plan_breakdown_requested" ||
|
|
299
|
+
reason === "packet_wave_complete_sync_needed" ||
|
|
300
|
+
reason === "prepare_for_merge_requested" ||
|
|
301
|
+
reason === "retry_requested" ||
|
|
302
|
+
reason === "cancel_requested" ||
|
|
303
|
+
reason === "mcp_skip_requested" ||
|
|
304
|
+
reason === "workstream_unlinked_pr" ||
|
|
305
|
+
reason === "unknown_phase_needs_attention" ||
|
|
306
|
+
(reason === "pr_review_requested" &&
|
|
307
|
+
(!lastHandledReviewHeadSha ||
|
|
308
|
+
(currentPullRequestHeadSha !== undefined && currentPullRequestHeadSha !== lastHandledReviewHeadSha))));
|
|
309
|
+
}
|
|
310
|
+
function isAgentAuthoredComment(body) {
|
|
311
|
+
return body.trimStart().startsWith("🤖");
|
|
312
|
+
}
|
|
313
|
+
function isCancelRequest(body) {
|
|
314
|
+
return /\bcancel\b/i.test(body) || /\babandon\b/i.test(body);
|
|
315
|
+
}
|
|
316
|
+
function findMatchingMcpBlocker(workstream, taskId, mcpHandle) {
|
|
317
|
+
return (workstream.mcpBlockers ?? []).find((blocker) => blocker.blockedTaskId === taskId && blocker.mcpHandle === mcpHandle);
|
|
318
|
+
}
|
|
319
|
+
function buildIssueKey(repoKey, number) {
|
|
320
|
+
return `${repoKey}:issue:${number}`;
|
|
321
|
+
}
|
|
322
|
+
function buildPrKey(repoKey, number) {
|
|
323
|
+
return `${repoKey}:pr:${number}`;
|
|
324
|
+
}
|
|
325
|
+
function buildEntityId(kind, repoKey, number) {
|
|
326
|
+
return `${kind}:${repoKey}:${number}`;
|
|
327
|
+
}
|
|
328
|
+
function compareComments(left, right) {
|
|
329
|
+
const repoComparison = left.repoSlug.localeCompare(right.repoSlug);
|
|
330
|
+
if (repoComparison !== 0) {
|
|
331
|
+
return repoComparison;
|
|
332
|
+
}
|
|
333
|
+
if (left.parentNumber !== right.parentNumber) {
|
|
334
|
+
return left.parentNumber - right.parentNumber;
|
|
335
|
+
}
|
|
336
|
+
return left.id.localeCompare(right.id);
|
|
337
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { validateCapabilities } from "./validator.js";
|
|
2
|
+
export const SENSOR_TOOL_MAP = {
|
|
3
|
+
"issues-and-prs": ["get_issue", "get_issue_comments", "get_pr", "get_pr_comments"],
|
|
4
|
+
"package-docs": ["resolve-library-id", "get-library-docs", "query-docs"],
|
|
5
|
+
"repository-context": ["search_code"],
|
|
6
|
+
};
|
|
7
|
+
export const CONTROLLER_TOOL_MAP = {
|
|
8
|
+
"comment-on-prs": ["post_issue_comment"],
|
|
9
|
+
"comment-on-issues": ["post_issue_comment"],
|
|
10
|
+
"open-issues": [],
|
|
11
|
+
"edit-issue-labels": [],
|
|
12
|
+
};
|
|
13
|
+
export function projectCapabilities(input) {
|
|
14
|
+
void input.taskKind;
|
|
15
|
+
const validation = validateCapabilities(input.capabilities, input.declaredMcps, input.mcpStatuses);
|
|
16
|
+
const sensorsByName = new Map(input.capabilities.sensors.map((sensor) => [sensor.name, sensor]));
|
|
17
|
+
const controllersByName = new Map(input.capabilities.controllers.map((controller) => [controller.name, controller]));
|
|
18
|
+
const registeredPublicBrokerToolNames = uniqueStrings(input.registeredPublicBrokerToolNames);
|
|
19
|
+
const providerRunAllowList = uniqueStrings(input.providerRunAllowList);
|
|
20
|
+
const declaredMcpHandles = new Set(input.declaredMcps.map((mcp) => mcp.handle));
|
|
21
|
+
const skippedCapabilityNames = input.skippedCapabilityNames ?? new Set();
|
|
22
|
+
const blocked = validation.blocked.map((capability) => skippedCapabilityNames.has(capability.name)
|
|
23
|
+
? skippedCapability(capability.kind, capability.name, capability.uses)
|
|
24
|
+
: capability);
|
|
25
|
+
const sensors = [];
|
|
26
|
+
const controllers = [];
|
|
27
|
+
for (const capability of validation.valid) {
|
|
28
|
+
if (skippedCapabilityNames.has(capability.name)) {
|
|
29
|
+
blocked.push(skippedCapability(capability.kind, capability.name, capability.uses));
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (capability.kind === "sensor") {
|
|
33
|
+
const sensor = sensorsByName.get(capability.name);
|
|
34
|
+
if (!sensor) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const tools = projectToolsForCapability(SENSOR_TOOL_MAP[sensor.observes], registeredPublicBrokerToolNames, providerRunAllowList, sensor.uses, declaredMcpHandles);
|
|
38
|
+
if (tools.length === 0) {
|
|
39
|
+
blocked.push(blockedCapability(capability.kind, sensor.name, sensor.uses, toolsBlockedReason(SENSOR_TOOL_MAP[sensor.observes], registeredPublicBrokerToolNames, sensor.uses, declaredMcpHandles)));
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
sensors.push(buildVisibleSensor(sensor, tools));
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const controller = controllersByName.get(capability.name);
|
|
46
|
+
if (!controller) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const mappedTools = capabilityToolsForController(controller.grants);
|
|
50
|
+
const tools = projectToolsForCapability(mappedTools, registeredPublicBrokerToolNames, providerRunAllowList, controller.uses, declaredMcpHandles);
|
|
51
|
+
if (tools.length === 0) {
|
|
52
|
+
blocked.push(blockedCapability(capability.kind, controller.name, controller.uses, toolsBlockedReason(mappedTools, registeredPublicBrokerToolNames, controller.uses, declaredMcpHandles)));
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
controllers.push(buildVisibleController(controller, tools));
|
|
56
|
+
}
|
|
57
|
+
return { sensors, controllers, blocked };
|
|
58
|
+
}
|
|
59
|
+
export function applySkippedCapabilityProjection(capabilities, skippedCapabilityNames) {
|
|
60
|
+
if (skippedCapabilityNames.size === 0) {
|
|
61
|
+
return capabilities;
|
|
62
|
+
}
|
|
63
|
+
const skippedFromSensors = capabilities.sensors
|
|
64
|
+
.filter((sensor) => skippedCapabilityNames.has(sensor.name))
|
|
65
|
+
.map((sensor) => skippedCapability("sensor", sensor.name, sensor.uses));
|
|
66
|
+
const skippedFromControllers = capabilities.controllers
|
|
67
|
+
.filter((controller) => skippedCapabilityNames.has(controller.name))
|
|
68
|
+
.map((controller) => skippedCapability("controller", controller.name, controller.uses));
|
|
69
|
+
const blocked = capabilities.blocked.map((capability) => skippedCapabilityNames.has(capability.name)
|
|
70
|
+
? skippedCapability(capability.kind, capability.name, capability.uses)
|
|
71
|
+
: capability);
|
|
72
|
+
const blockedNames = new Set(blocked.map((capability) => capability.name));
|
|
73
|
+
for (const capability of [...skippedFromSensors, ...skippedFromControllers]) {
|
|
74
|
+
if (!blockedNames.has(capability.name)) {
|
|
75
|
+
blocked.push(capability);
|
|
76
|
+
blockedNames.add(capability.name);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
sensors: capabilities.sensors.filter((sensor) => !skippedCapabilityNames.has(sensor.name)),
|
|
81
|
+
controllers: capabilities.controllers.filter((controller) => !skippedCapabilityNames.has(controller.name)),
|
|
82
|
+
blocked
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function buildVisibleSensor(sensor, tools) {
|
|
86
|
+
return {
|
|
87
|
+
name: sensor.name,
|
|
88
|
+
uses: sensor.uses,
|
|
89
|
+
observes: sensor.observes,
|
|
90
|
+
...(sensor.cadence !== undefined ? { cadence: sensor.cadence } : {}),
|
|
91
|
+
...(sensor.scope !== undefined ? { scope: sensor.scope } : {}),
|
|
92
|
+
tools,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function buildVisibleController(controller, tools) {
|
|
96
|
+
return {
|
|
97
|
+
name: controller.name,
|
|
98
|
+
uses: controller.uses,
|
|
99
|
+
grants: [...controller.grants],
|
|
100
|
+
approval: controller.approval,
|
|
101
|
+
tools,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function projectToolsForCapability(mappedTools, registeredPublicBrokerToolNames, providerRunAllowList, capabilityUses, declaredMcpHandles) {
|
|
105
|
+
const registeredMatches = registeredPublicBrokerToolNames.filter((publicToolName) => mappedTools.some((mappedTool) => publicToolMatchesMappedCapabilityTool(publicToolName, mappedTool, capabilityUses, declaredMcpHandles)));
|
|
106
|
+
if (registeredMatches.length === 0) {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
return registeredMatches.filter((publicToolName) => providerRunAllowList.some((allowedToolName) => providerToolAllowsPublicTool(publicToolName, allowedToolName)));
|
|
110
|
+
}
|
|
111
|
+
function toolsBlockedReason(mappedTools, registeredPublicBrokerToolNames, capabilityUses, declaredMcpHandles) {
|
|
112
|
+
const registeredMatches = registeredPublicBrokerToolNames.filter((publicToolName) => mappedTools.some((mappedTool) => publicToolMatchesMappedCapabilityTool(publicToolName, mappedTool, capabilityUses, declaredMcpHandles)));
|
|
113
|
+
return registeredMatches.length === 0
|
|
114
|
+
? "capability_tools_unavailable"
|
|
115
|
+
: "capability_tools_not_allowed";
|
|
116
|
+
}
|
|
117
|
+
function capabilityToolsForController(grants) {
|
|
118
|
+
const tools = grants.flatMap((grant) => CONTROLLER_TOOL_MAP[grant] ?? []);
|
|
119
|
+
return uniqueStrings(tools);
|
|
120
|
+
}
|
|
121
|
+
function blockedCapability(kind, name, uses, reason) {
|
|
122
|
+
return {
|
|
123
|
+
name,
|
|
124
|
+
kind,
|
|
125
|
+
uses,
|
|
126
|
+
reason,
|
|
127
|
+
message: reason === "capability_tools_unavailable"
|
|
128
|
+
? "No registered broker tool matches the capability's mapped tool set."
|
|
129
|
+
: "Mapped capability tools are not in the active provider/run allow-list.",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function skippedCapability(kind, name, uses) {
|
|
133
|
+
return {
|
|
134
|
+
kind,
|
|
135
|
+
name,
|
|
136
|
+
uses,
|
|
137
|
+
reason: "capability_skipped_by_user",
|
|
138
|
+
message: "User explicitly skipped this MCP-backed capability for the current task."
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function publicToolNameMatches(publicToolName, candidateToolName) {
|
|
142
|
+
if (candidateToolName === "*") {
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
if (candidateToolName.endsWith(".*")) {
|
|
146
|
+
return publicToolName.startsWith(candidateToolName.slice(0, -1));
|
|
147
|
+
}
|
|
148
|
+
return (publicToolName === candidateToolName ||
|
|
149
|
+
publicToolName.endsWith(`.${candidateToolName}`) ||
|
|
150
|
+
candidateToolName.endsWith(`.${publicToolName}`));
|
|
151
|
+
}
|
|
152
|
+
function publicToolMatchesMappedCapabilityTool(publicToolName, mappedToolName, capabilityUses, declaredMcpHandles) {
|
|
153
|
+
const parsed = splitPublicToolName(publicToolName);
|
|
154
|
+
if (parsed && declaredMcpHandles.has(parsed.namespace)) {
|
|
155
|
+
return parsed.namespace === capabilityUses && publicToolNameMatches(parsed.localName, mappedToolName);
|
|
156
|
+
}
|
|
157
|
+
return publicToolNameMatches(publicToolName, mappedToolName);
|
|
158
|
+
}
|
|
159
|
+
function splitPublicToolName(publicToolName) {
|
|
160
|
+
const dotIndex = publicToolName.indexOf(".");
|
|
161
|
+
if (dotIndex <= 0 || dotIndex === publicToolName.length - 1) {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
namespace: publicToolName.slice(0, dotIndex),
|
|
166
|
+
localName: publicToolName.slice(dotIndex + 1)
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function providerToolAllowsPublicTool(publicToolName, allowedToolName) {
|
|
170
|
+
if (publicToolName.includes(".")) {
|
|
171
|
+
return allowedToolName === publicToolName;
|
|
172
|
+
}
|
|
173
|
+
return publicToolNameMatches(publicToolName, allowedToolName);
|
|
174
|
+
}
|
|
175
|
+
export function deriveProjectedCapabilityToolNames(capabilities) {
|
|
176
|
+
return uniqueStrings([
|
|
177
|
+
...capabilities.sensors.flatMap((sensor) => sensor.tools),
|
|
178
|
+
...capabilities.controllers.flatMap((controller) => controller.tools),
|
|
179
|
+
]);
|
|
180
|
+
}
|
|
181
|
+
function uniqueStrings(values) {
|
|
182
|
+
return Array.from(new Set(values.filter((value) => value.trim().length > 0)));
|
|
183
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|