@wardby/cli 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +22 -3
- package/README.md +11 -9
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +1 -0
- package/dist/cli.js +68 -10
- package/dist/coding/base-commit.d.ts +6 -0
- package/dist/coding/base-commit.js +12 -0
- package/dist/coding/protocol.d.ts +17 -1
- package/dist/coding/protocol.js +17 -6
- package/dist/coding/provider.d.ts +6 -1
- package/dist/coding/provider.js +16 -9
- package/dist/config/providers.d.ts +25 -0
- package/dist/config/providers.js +71 -0
- package/dist/core/attribution.d.ts +101 -0
- package/dist/core/attribution.js +208 -0
- package/dist/core/budget-groups.d.ts +17 -10
- package/dist/core/budget-groups.js +15 -12
- package/dist/core/coding-queue.d.ts +3 -0
- package/dist/core/coding-queue.js +6 -2
- package/dist/core/coding-service-status.d.ts +11 -0
- package/dist/core/coding-service-status.js +17 -0
- package/dist/core/cost-report.d.ts +88 -0
- package/dist/core/cost-report.js +248 -0
- package/dist/core/dispatch.d.ts +42 -2
- package/dist/core/dispatch.js +144 -26
- package/dist/core/engine-native.js +17 -4
- package/dist/core/glob.d.ts +10 -0
- package/dist/core/glob.js +33 -0
- package/dist/core/host-events.d.ts +24 -1
- package/dist/core/host-events.js +341 -1
- package/dist/core/host-status.d.ts +19 -2
- package/dist/core/host-status.js +47 -30
- package/dist/core/issue-bridge.d.ts +60 -0
- package/dist/core/issue-bridge.js +189 -0
- package/dist/core/issue-dedupe.d.ts +70 -0
- package/dist/core/issue-dedupe.js +255 -0
- package/dist/core/issue-events.d.ts +42 -0
- package/dist/core/issue-events.js +155 -0
- package/dist/core/issue-status.d.ts +29 -0
- package/dist/core/issue-status.js +241 -0
- package/dist/core/issue-tracker-tools.d.ts +64 -0
- package/dist/core/issue-tracker-tools.js +850 -0
- package/dist/core/model-usage.d.ts +10 -0
- package/dist/core/model-usage.js +24 -0
- package/dist/core/reconciler.d.ts +8 -4
- package/dist/core/reconciler.js +15 -4
- package/dist/core/review-host-tools.js +10 -3
- package/dist/core/run-pricing.d.ts +61 -0
- package/dist/core/run-pricing.js +56 -0
- package/dist/core/runner.d.ts +5 -2
- package/dist/core/runner.js +178 -27
- package/dist/core/scheduler.d.ts +4 -1
- package/dist/core/scheduler.js +3 -2
- package/dist/core/self-defects.d.ts +80 -0
- package/dist/core/self-defects.js +180 -0
- package/dist/core/tool-names.js +3 -0
- package/dist/core/webhooks.d.ts +9 -1
- package/dist/core/webhooks.js +19 -1
- package/dist/env.js +6 -1
- package/dist/generated/prisma/browser.d.ts +66 -0
- package/dist/generated/prisma/client.d.ts +66 -0
- package/dist/generated/prisma/commonInputTypes.d.ts +122 -52
- package/dist/generated/prisma/enums.d.ts +7 -0
- package/dist/generated/prisma/enums.js +6 -0
- package/dist/generated/prisma/internal/class.d.ts +99 -0
- package/dist/generated/prisma/internal/class.js +4 -4
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +826 -1
- package/dist/generated/prisma/internal/prismaNamespace.js +135 -2
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +142 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +135 -2
- package/dist/generated/prisma/models/Agent.d.ts +389 -1
- package/dist/generated/prisma/models/AgentIssueProject.d.ts +1838 -0
- package/dist/generated/prisma/models/AgentIssueProject.js +1 -0
- package/dist/generated/prisma/models/AgentRepository.d.ts +1 -1
- package/dist/generated/prisma/models/AuthUser.d.ts +1 -1
- package/dist/generated/prisma/models/CodingProxySession.d.ts +73 -1
- package/dist/generated/prisma/models/CodingRun.d.ts +130 -1
- package/dist/generated/prisma/models/CodingRunServiceStatus.d.ts +1404 -0
- package/dist/generated/prisma/models/CodingRunServiceStatus.js +1 -0
- package/dist/generated/prisma/models/IssueFingerprint.d.ts +1183 -0
- package/dist/generated/prisma/models/IssueFingerprint.js +1 -0
- package/dist/generated/prisma/models/IssuePullRequest.d.ts +1255 -0
- package/dist/generated/prisma/models/IssuePullRequest.js +1 -0
- package/dist/generated/prisma/models/ModelCatalogEntry.d.ts +1322 -0
- package/dist/generated/prisma/models/ModelCatalogEntry.js +1 -0
- package/dist/generated/prisma/models/Run.d.ts +933 -1
- package/dist/generated/prisma/models/RunAttribution.d.ts +1259 -0
- package/dist/generated/prisma/models/RunAttribution.js +1 -0
- package/dist/generated/prisma/models/RunIssueStatus.d.ts +1199 -0
- package/dist/generated/prisma/models/RunIssueStatus.js +1 -0
- package/dist/generated/prisma/models/RunModelUsage.d.ts +1316 -0
- package/dist/generated/prisma/models/RunModelUsage.js +1 -0
- package/dist/generated/prisma/models/WorkItem.d.ts +1408 -0
- package/dist/generated/prisma/models/WorkItem.js +1 -0
- package/dist/generated/prisma/models.d.ts +9 -0
- package/dist/help-index.json +355 -16
- package/dist/import/neutral-schema.d.ts +16 -16
- package/dist/knowledge/check.d.ts +13 -0
- package/dist/knowledge/check.js +69 -0
- package/dist/knowledge/cli.d.ts +14 -0
- package/dist/knowledge/cli.js +67 -0
- package/dist/knowledge/concept.d.ts +54 -0
- package/dist/knowledge/concept.js +78 -0
- package/dist/knowledge/note.d.ts +11 -0
- package/dist/knowledge/note.js +39 -0
- package/dist/knowledge/relevance.d.ts +11 -0
- package/dist/knowledge/relevance.js +14 -0
- package/dist/knowledge/span-hash.d.ts +3 -0
- package/dist/knowledge/span-hash.js +16 -0
- package/dist/mcp/auth/access.d.ts +4 -2
- package/dist/mcp/auth/ownership.d.ts +9 -9
- package/dist/mcp/auth/resource-server.d.ts +3 -1
- package/dist/mcp/auth/resource-server.js +18 -3
- package/dist/mcp/auth/self-hosted/credentials.d.ts +3 -3
- package/dist/mcp/auth/self-hosted/session.d.ts +5 -5
- package/dist/mcp/context.d.ts +3 -0
- package/dist/mcp/host-events/deliveries.d.ts +9 -0
- package/dist/mcp/host-events/deliveries.js +17 -0
- package/dist/mcp/host-events/github-ingress.d.ts +4 -2
- package/dist/mcp/host-events/github-ingress.js +4 -13
- package/dist/mcp/host-events/jira-ingress.d.ts +29 -0
- package/dist/mcp/host-events/jira-ingress.js +92 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +87 -9
- package/dist/mcp/server.js +5 -2
- package/dist/mcp/tools/agents.js +74 -3
- package/dist/mcp/tools/cost-report.d.ts +8 -0
- package/dist/mcp/tools/cost-report.js +60 -0
- package/dist/mcp/tools/issue-projects.d.ts +2 -0
- package/dist/mcp/tools/issue-projects.js +238 -0
- package/dist/mcp/tools/model-catalog.d.ts +22 -0
- package/dist/mcp/tools/model-catalog.js +423 -0
- package/dist/mcp/tools/repositories.js +2 -1
- package/dist/mcp/tools/tools.d.ts +2 -2
- package/dist/mcp/tools/trigger.js +33 -5
- package/dist/mcp/transport/streamable-http.d.ts +5 -0
- package/dist/mcp/transport/streamable-http.js +23 -1
- package/dist/mcp/webhooks/ingress.d.ts +2 -1
- package/dist/mcp/webhooks/ingress.js +9 -2
- package/dist/providers/auth/self-hosted.d.ts +8 -1
- package/dist/providers/auth/self-hosted.js +39 -2
- package/dist/providers/coding-proxy/memory-ledger.d.ts +1 -1
- package/dist/providers/coding-proxy/memory-ledger.js +10 -1
- package/dist/providers/coding-proxy/metering.d.ts +2 -1
- package/dist/providers/coding-proxy/metering.js +13 -2
- package/dist/providers/coding-proxy/prisma-ledger.js +59 -6
- package/dist/providers/coding-proxy/proxy.d.ts +12 -2
- package/dist/providers/coding-proxy/proxy.js +92 -30
- package/dist/providers/coding-proxy/types.d.ts +18 -1
- package/dist/providers/coding-proxy/types.js +12 -1
- package/dist/providers/engine/types.d.ts +19 -0
- package/dist/providers/executor/composition.js +9 -1
- package/dist/providers/executor/container.d.ts +30 -2
- package/dist/providers/executor/container.js +98 -17
- package/dist/providers/executor/dbos.d.ts +2 -0
- package/dist/providers/executor/dbos.js +7 -5
- package/dist/providers/executor/routing.d.ts +6 -0
- package/dist/providers/executor/routing.js +5 -0
- package/dist/providers/executor/types.d.ts +12 -0
- package/dist/providers/issue-tracker/adf.d.ts +31 -0
- package/dist/providers/issue-tracker/adf.js +181 -0
- package/dist/providers/issue-tracker/index.d.ts +5 -0
- package/dist/providers/issue-tracker/index.js +12 -0
- package/dist/providers/issue-tracker/jira-client.d.ts +41 -0
- package/dist/providers/issue-tracker/jira-client.js +151 -0
- package/dist/providers/issue-tracker/jira-events.d.ts +3 -0
- package/dist/providers/issue-tracker/jira-events.js +98 -0
- package/dist/providers/issue-tracker/jira.d.ts +116 -0
- package/dist/providers/issue-tracker/jira.js +502 -0
- package/dist/providers/issue-tracker/types.d.ts +269 -0
- package/dist/providers/issue-tracker/types.js +16 -0
- package/dist/providers/jobs/docker.d.ts +5 -1
- package/dist/providers/jobs/docker.js +61 -33
- package/dist/providers/jobs/kubernetes.d.ts +3 -0
- package/dist/providers/jobs/kubernetes.js +44 -4
- package/dist/providers/jobs/service-state.d.ts +22 -0
- package/dist/providers/jobs/service-state.js +17 -0
- package/dist/providers/llm/anthropic.d.ts +3 -3
- package/dist/providers/llm/anthropic.js +3 -9
- package/dist/providers/llm/bedrock.d.ts +3 -3
- package/dist/providers/llm/bedrock.js +3 -9
- package/dist/providers/llm/catalog-lookup.d.ts +10 -0
- package/dist/providers/llm/catalog-lookup.js +15 -0
- package/dist/providers/llm/catalog-shipped.d.ts +18 -0
- package/dist/providers/llm/catalog-shipped.js +197 -0
- package/dist/providers/llm/catalog-store.d.ts +58 -0
- package/dist/providers/llm/catalog-store.js +138 -0
- package/dist/providers/llm/catalog-types.d.ts +66 -0
- package/dist/providers/llm/catalog-types.js +64 -0
- package/dist/providers/llm/catalog.d.ts +61 -0
- package/dist/providers/llm/catalog.js +147 -0
- package/dist/providers/llm/claude-provider.d.ts +10 -14
- package/dist/providers/llm/claude-provider.js +11 -6
- package/dist/providers/llm/index.d.ts +9 -6
- package/dist/providers/llm/index.js +8 -5
- package/dist/providers/llm/openai.d.ts +14 -5
- package/dist/providers/llm/openai.js +24 -14
- package/dist/providers/llm/pricing-core.d.ts +5 -3
- package/dist/providers/llm/registration.js +8 -12
- package/dist/providers/llm/routing.d.ts +18 -17
- package/dist/providers/llm/routing.js +40 -24
- package/dist/providers/review-host/github-events.js +47 -1
- package/dist/providers/review-host/github.js +7 -6
- package/dist/providers/review-host/types.d.ts +25 -0
- package/dist/providers/vcs/git.js +2 -22
- package/dist/providers/vcs/github.d.ts +20 -0
- package/dist/providers/vcs/github.js +28 -2
- package/dist/providers/vcs/types.d.ts +6 -0
- package/dist/quickstart/index.d.ts +8 -0
- package/dist/quickstart/index.js +34 -34
- package/dist/serve.js +8 -2
- package/dist/viewer/api-schema.d.ts +2757 -0
- package/dist/viewer/api-schema.js +165 -0
- package/dist/viewer/build-schemas.d.ts +2 -0
- package/dist/viewer/build-schemas.js +18 -0
- package/dist/viewer/event-bus.d.ts +38 -0
- package/dist/viewer/event-bus.js +232 -0
- package/dist/viewer/graph.d.ts +40 -0
- package/dist/viewer/graph.js +243 -0
- package/dist/viewer/http.d.ts +30 -0
- package/dist/viewer/http.js +133 -0
- package/dist/viewer/run-detail.d.ts +4 -0
- package/dist/viewer/run-detail.js +61 -0
- package/dist/wardby-bin.js +5 -0
- package/docs/README.md +10 -0
- package/docs/agent-recipes.md +383 -0
- package/docs/code-review-agents.md +29 -2
- package/docs/coding-agent-setup.md +3 -0
- package/docs/coding-worker-isolation.md +39 -5
- package/docs/getting-started-gke.md +28 -11
- package/docs/getting-started-identity-provider.md +49 -38
- package/docs/getting-started.md +14 -0
- package/docs/jira-agents.md +649 -0
- package/docs/knowledge.md +387 -0
- package/docs/models.md +221 -0
- package/docs/security-deployment.md +19 -9
- package/docs/viewer-api.md +142 -0
- package/help/admin-viewer.md +39 -0
- package/help/agent-recipes.md +173 -0
- package/help/architecture-agent.md +189 -0
- package/help/builder-agent.md +80 -0
- package/help/code-review-agents.md +6 -0
- package/help/cost-attribution.md +67 -0
- package/help/creating-agents.md +22 -0
- package/help/deploy-gke.md +6 -0
- package/help/errors/model-unavailable.md +63 -0
- package/help/getting-started.md +1 -0
- package/help/github.md +18 -0
- package/help/identity-and-access.md +8 -3
- package/help/jira.md +135 -0
- package/help/knowledge.md +47 -0
- package/help/models.md +90 -0
- package/help/operating-agents.md +7 -1
- package/help/troubleshooting/budgets.md +6 -0
- package/package.json +5 -2
- package/prisma/migrations/20260930000000_jira_issue_projects/migration.sql +34 -0
- package/prisma/migrations/20261001000000_jira_phase2_allowlists/migration.sql +3 -0
- package/prisma/migrations/20261001010000_jira_link_types_allowlist/migration.sql +2 -0
- package/prisma/migrations/20261002000000_jira_coding_bridge/migration.sql +28 -0
- package/prisma/migrations/20261002010000_jira_issue_creation/migration.sql +25 -0
- package/prisma/migrations/20261003000000_issue_cost_attribution/migration.sql +56 -0
- package/prisma/migrations/20261003010000_coding_run_service_status/migration.sql +23 -0
- package/prisma/migrations/20261003020000_viewer_notify/migration.sql +54 -0
- package/prisma/migrations/20261003030000_viewer_notify_fixes/migration.sql +47 -0
- package/prisma/migrations/20261003040000_viewer_indexes/migration.sql +12 -0
- package/prisma/migrations/20261004000000_model_catalog/migration.sql +26 -0
- package/prisma/schema.prisma +258 -2
- package/dist/mcp/tools/models.d.ts +0 -8
- package/dist/mcp/tools/models.js +0 -15
- package/dist/providers/llm/pricing-anthropic.d.ts +0 -14
- package/dist/providers/llm/pricing-anthropic.js +0 -48
- package/dist/providers/llm/pricing-bedrock-claude.d.ts +0 -20
- package/dist/providers/llm/pricing-bedrock-claude.js +0 -46
- package/dist/providers/llm/pricing.d.ts +0 -30
- package/dist/providers/llm/pricing.js +0 -74
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The status comment for a run started by a Jira issue event: "working on
|
|
3
|
+
* it" right after the webhook is answered, edited once with the outcome.
|
|
4
|
+
* Same lifecycle as host-status.ts (GitHub mentions), reusing its outcome
|
|
5
|
+
* text. Best effort throughout: nothing here throws.
|
|
6
|
+
*/
|
|
7
|
+
import { Prisma } from "#prisma";
|
|
8
|
+
import { projectOf } from "../providers/issue-tracker/types.js";
|
|
9
|
+
import { collectRunOutcome, outcomeBody, runLine, workingBody } from "./host-status.js";
|
|
10
|
+
import { recordPullRequests } from "./issue-bridge.js";
|
|
11
|
+
import { logger } from "./logger.js";
|
|
12
|
+
const log = logger.child({ module: "issue-status" });
|
|
13
|
+
const TERMINAL = new Set(["succeeded", "failed", "refused", "lost", "budget_exhausted", "cancelled"]);
|
|
14
|
+
const ORPHAN_GRACE_MS = 2 * 60 * 1000;
|
|
15
|
+
const ORPHAN_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
16
|
+
const ORPHAN_BATCH = 20;
|
|
17
|
+
export function toJiraMarkdown(text) {
|
|
18
|
+
return text
|
|
19
|
+
.replace(/<sub>(.*?)<\/sub>/g, "_$1_")
|
|
20
|
+
.replace(/\b([\w.-]+\/[\w.-]+)#(\d+)\b/g, (_m, repo, n) => `[${repo}#${n}](https://github.com/${repo}/pull/${n})`);
|
|
21
|
+
}
|
|
22
|
+
export function issueStatusRow(event, runId, visibilityRole) {
|
|
23
|
+
return { runId, provider: "jira", issueKey: event.issueKey, visibilityRole };
|
|
24
|
+
}
|
|
25
|
+
export function formatSpendLine(input) {
|
|
26
|
+
const parts = [`Agent spend: $${input.treeUsd.toFixed(4)} this run`];
|
|
27
|
+
if (input.issueUsd !== null)
|
|
28
|
+
parts.push(`$${input.issueUsd.toFixed(4)} on this issue so far`);
|
|
29
|
+
if (input.models.length > 0)
|
|
30
|
+
parts.push(input.models.map((m) => `${m.model} $${m.costUsd.toFixed(4)}`).join(", "));
|
|
31
|
+
return parts.join(" · ");
|
|
32
|
+
}
|
|
33
|
+
/** The whole run tree under `runId` (every depth), the issue's total across all its runs, and the tree's models. */
|
|
34
|
+
export async function spendLine(db, runId) {
|
|
35
|
+
try {
|
|
36
|
+
const tree = Prisma.sql `
|
|
37
|
+
WITH RECURSIVE tree AS (
|
|
38
|
+
SELECT "id" FROM "Run" WHERE "id" = ${runId}
|
|
39
|
+
-- UNION, not UNION ALL: a parentRunId cycle (never written, but not a constraint) can't recurse forever.
|
|
40
|
+
UNION
|
|
41
|
+
SELECT r."id" FROM "Run" r JOIN tree t ON r."parentRunId" = t."id"
|
|
42
|
+
)`;
|
|
43
|
+
const [treeTotal, issueTotal, models] = await Promise.all([
|
|
44
|
+
db.$queryRaw `${tree} SELECT SUM(r."costUsd")::text AS usd FROM "Run" r JOIN tree t ON r."id" = t."id"`,
|
|
45
|
+
db.$queryRaw `
|
|
46
|
+
SELECT SUM(r."costUsd")::text AS usd
|
|
47
|
+
FROM "RunAttribution" a
|
|
48
|
+
JOIN "Run" r ON r."id" = a."runId"
|
|
49
|
+
WHERE a."workItemId" = (SELECT "workItemId" FROM "RunAttribution" WHERE "runId" = ${runId})`,
|
|
50
|
+
db.$queryRaw `${tree}
|
|
51
|
+
SELECT u."model", SUM(u."costUsd")::text AS usd
|
|
52
|
+
FROM "RunModelUsage" u JOIN tree t ON u."runId" = t."id"
|
|
53
|
+
GROUP BY u."model" ORDER BY SUM(u."costUsd") DESC`,
|
|
54
|
+
]);
|
|
55
|
+
const treeUsd = Number(treeTotal[0]?.usd ?? 0);
|
|
56
|
+
if (!Number.isFinite(treeUsd))
|
|
57
|
+
return "";
|
|
58
|
+
return formatSpendLine({
|
|
59
|
+
treeUsd,
|
|
60
|
+
issueUsd: issueTotal[0]?.usd == null ? null : Number(issueTotal[0].usd),
|
|
61
|
+
models: models.map((m) => ({ model: m.model, costUsd: Number(m.usd) })),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
log.warn({ err, runId }, "could not compute the agent spend for the issue comment");
|
|
66
|
+
return "";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Insert the spend line above the trailing footer, which must stay the last line. */
|
|
70
|
+
function withSpend(body, spend) {
|
|
71
|
+
if (!spend)
|
|
72
|
+
return body;
|
|
73
|
+
const trimmed = body.trimEnd();
|
|
74
|
+
const at = trimmed.lastIndexOf("\n");
|
|
75
|
+
if (at < 0)
|
|
76
|
+
return `${spend}\n\n${trimmed}`;
|
|
77
|
+
return `${trimmed.slice(0, at).trimEnd()}\n\n${spend}\n\n${trimmed.slice(at + 1)}`;
|
|
78
|
+
}
|
|
79
|
+
export async function postIssueWorkingStatus(db, trackers, runId) {
|
|
80
|
+
try {
|
|
81
|
+
const status = await db.runIssueStatus.findUnique({ where: { runId } });
|
|
82
|
+
if (!status || status.commentId || status.completedAt)
|
|
83
|
+
return;
|
|
84
|
+
const tracker = trackers[status.provider];
|
|
85
|
+
if (!tracker)
|
|
86
|
+
return;
|
|
87
|
+
const posted = await tracker.comment(status.issueKey, {
|
|
88
|
+
markdown: toJiraMarkdown(workingBody(runId)),
|
|
89
|
+
...(status.visibilityRole ? { visibilityRole: status.visibilityRole } : {}),
|
|
90
|
+
});
|
|
91
|
+
const claimed = await db.runIssueStatus.updateMany({
|
|
92
|
+
where: { runId, commentId: null, completedAt: null },
|
|
93
|
+
data: { commentId: posted.id },
|
|
94
|
+
});
|
|
95
|
+
if (claimed.count === 0) {
|
|
96
|
+
log.warn({ runId }, "status comment posted after the outcome; leaving both");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const run = await db.run.findUnique({ where: { id: runId }, select: { id: true, status: true, finalText: true } });
|
|
100
|
+
if (run && TERMINAL.has(run.status))
|
|
101
|
+
await completeIssueStatus(db, run, trackers);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
log.warn({ err, runId }, "could not post the issue status comment");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** The run's agent's live link to the issue's project, or null (also when it cannot be determined: fail closed). */
|
|
108
|
+
async function currentLink(db, runId, issueKey, provider) {
|
|
109
|
+
const run = await db.run.findUnique({ where: { id: runId }, select: { agentId: true } });
|
|
110
|
+
if (!run?.agentId)
|
|
111
|
+
return null;
|
|
112
|
+
const link = await db.agentIssueProject.findUnique({
|
|
113
|
+
where: { agentId_provider_projectKey: { agentId: run.agentId, provider, projectKey: projectOf(issueKey) } },
|
|
114
|
+
select: { access: true, commentVisibilityRole: true, onPullRequestOpened: true },
|
|
115
|
+
});
|
|
116
|
+
return link ? { agentId: run.agentId, ...link } : null;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The coding runs (of `runIds`) that continued a pull request opened for a
|
|
120
|
+
* different issue: their PR belongs to that issue, not this one. A continued
|
|
121
|
+
* PR with no issue, or this same issue, is fine.
|
|
122
|
+
*/
|
|
123
|
+
async function continuedForAnotherIssue(db, runIds, issue) {
|
|
124
|
+
if (runIds.length === 0)
|
|
125
|
+
return new Set();
|
|
126
|
+
const rows = await db.codingRun.findMany({
|
|
127
|
+
where: { runId: { in: runIds } },
|
|
128
|
+
select: { runId: true, rootCodingRun: { select: { issueProvider: true, issueKey: true } } },
|
|
129
|
+
});
|
|
130
|
+
return new Set(rows
|
|
131
|
+
.filter(({ rootCodingRun: root }) => {
|
|
132
|
+
if (!root?.issueKey)
|
|
133
|
+
return false;
|
|
134
|
+
return root.issueKey !== issue.issueKey || root.issueProvider !== issue.provider;
|
|
135
|
+
})
|
|
136
|
+
.map((row) => row.runId));
|
|
137
|
+
}
|
|
138
|
+
export async function completeIssueStatus(db, run, trackers, opts = {}) {
|
|
139
|
+
if (!trackers)
|
|
140
|
+
return;
|
|
141
|
+
try {
|
|
142
|
+
const status = await db.runIssueStatus.findUnique({ where: { runId: run.id } });
|
|
143
|
+
if (!status || status.completedAt)
|
|
144
|
+
return;
|
|
145
|
+
if (!status.commentId && !opts.postIfMissing)
|
|
146
|
+
return;
|
|
147
|
+
const tracker = trackers[status.provider];
|
|
148
|
+
if (!tracker)
|
|
149
|
+
return;
|
|
150
|
+
// The link may have been removed or downgraded to read since the run
|
|
151
|
+
// started: then report status only (no reply, no spend), never the agent's reply.
|
|
152
|
+
const link = await currentLink(db, run.id, status.issueKey, status.provider);
|
|
153
|
+
const writable = link?.access === "write";
|
|
154
|
+
const project = projectOf(status.issueKey);
|
|
155
|
+
let body;
|
|
156
|
+
if (writable) {
|
|
157
|
+
const { pullRequests, failedChildren, budgetSentence } = await collectRunOutcome(db, run);
|
|
158
|
+
const foreign = await continuedForAnotherIssue(db, pullRequests.flatMap((pr) => (pr.pullRequestUrl && pr.runId ? [pr.runId] : [])), status);
|
|
159
|
+
for (const runId of foreign) {
|
|
160
|
+
log.info({ runId: run.id, codingRunId: runId, issueKey: status.issueKey }, "not linking a pull request continued from another issue's run");
|
|
161
|
+
}
|
|
162
|
+
const bridged = pullRequests.flatMap((pr) => pr.pullRequestUrl && pr.runId && !foreign.has(pr.runId)
|
|
163
|
+
? [
|
|
164
|
+
{
|
|
165
|
+
codeProvider: pr.codeProvider,
|
|
166
|
+
repository: pr.repository,
|
|
167
|
+
number: pr.pullRequestNumber,
|
|
168
|
+
url: pr.pullRequestUrl,
|
|
169
|
+
openedByRunId: pr.runId,
|
|
170
|
+
outcome: pr.outcome,
|
|
171
|
+
},
|
|
172
|
+
]
|
|
173
|
+
: []);
|
|
174
|
+
const { notes } = bridged.length > 0
|
|
175
|
+
? await recordPullRequests(db, tracker, {
|
|
176
|
+
issueKey: status.issueKey,
|
|
177
|
+
issueProvider: status.provider,
|
|
178
|
+
agentId: link.agentId,
|
|
179
|
+
onPullRequestOpened: link.onPullRequestOpened,
|
|
180
|
+
pullRequests: bridged,
|
|
181
|
+
})
|
|
182
|
+
: { notes: [] };
|
|
183
|
+
const outcome = outcomeBody(run, "", pullRequests, failedChildren, {
|
|
184
|
+
budgetSentence,
|
|
185
|
+
noPullRequestText: "Done.",
|
|
186
|
+
// One comment per request: this status comment carries the agent's answer.
|
|
187
|
+
replyAsAnswer: true,
|
|
188
|
+
});
|
|
189
|
+
// Notes and spend go above the footer, which must stay the last line.
|
|
190
|
+
body = withSpend(withSpend(outcome, notes.join("\n")), await spendLine(db, run.id));
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
const why = link
|
|
194
|
+
? `this agent's link to ${project} is now read-only`
|
|
195
|
+
: `this agent is no longer linked to ${project}`;
|
|
196
|
+
body = `Stopped reporting: ${why}.\n\n${runLine(run.id)}`;
|
|
197
|
+
}
|
|
198
|
+
const markdown = toJiraMarkdown(body);
|
|
199
|
+
let commentId = status.commentId;
|
|
200
|
+
// Keys in the issue's own project (e.g. a related issue the reply names) become smart links.
|
|
201
|
+
const issueKeyProjects = writable ? [project] : [];
|
|
202
|
+
if (commentId)
|
|
203
|
+
await tracker.editComment(status.issueKey, commentId, { markdown, issueKeyProjects });
|
|
204
|
+
else if (writable) {
|
|
205
|
+
commentId = (await tracker.comment(status.issueKey, {
|
|
206
|
+
markdown,
|
|
207
|
+
issueKeyProjects,
|
|
208
|
+
...(link.commentVisibilityRole ? { visibilityRole: link.commentVisibilityRole } : {}),
|
|
209
|
+
})).id;
|
|
210
|
+
}
|
|
211
|
+
// Unlinked or read-only with no status comment yet: complete the row
|
|
212
|
+
// without posting; never create content where the agent may not write.
|
|
213
|
+
await db.runIssueStatus.update({ where: { runId: run.id }, data: { commentId, completedAt: new Date() } });
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
log.warn({ err, runId: run.id }, "could not complete the issue status comment");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
export async function closeOrphanedIssueStatuses(db, trackers, now = new Date()) {
|
|
220
|
+
const providers = Object.keys(trackers ?? {});
|
|
221
|
+
if (!trackers || providers.length === 0)
|
|
222
|
+
return;
|
|
223
|
+
const orphans = await db.runIssueStatus.findMany({
|
|
224
|
+
where: {
|
|
225
|
+
completedAt: null,
|
|
226
|
+
provider: { in: providers },
|
|
227
|
+
run: {
|
|
228
|
+
status: { notIn: ["pending", "running"] },
|
|
229
|
+
finishedAt: {
|
|
230
|
+
lte: new Date(now.getTime() - ORPHAN_GRACE_MS),
|
|
231
|
+
gte: new Date(now.getTime() - ORPHAN_MAX_AGE_MS),
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
select: { run: { select: { id: true, status: true, finalText: true } } },
|
|
236
|
+
orderBy: { run: { finishedAt: "desc" } },
|
|
237
|
+
take: ORPHAN_BATCH,
|
|
238
|
+
});
|
|
239
|
+
for (const { run } of orphans)
|
|
240
|
+
await completeIssueStatus(db, run, trackers, { postIfMissing: true });
|
|
241
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { LoadedTool } from "../providers/engine/types.js";
|
|
2
|
+
import { type FileIssueInput, type FileIssueResult } from "./issue-dedupe.js";
|
|
3
|
+
import { type IssueTrackerRegistry } from "../providers/issue-tracker/types.js";
|
|
4
|
+
export interface IssueProjectLink {
|
|
5
|
+
provider: "jira";
|
|
6
|
+
projectKey: string;
|
|
7
|
+
access: "read" | "write";
|
|
8
|
+
commentVisibilityRole: string | null;
|
|
9
|
+
/** Target status names jira_transition may move issues to (case-insensitive); empty = none. */
|
|
10
|
+
allowedTransitions: string[];
|
|
11
|
+
/** Field ids jira_update_fields may change; empty = none. */
|
|
12
|
+
writableFields: string[];
|
|
13
|
+
/** Issue link type names jira_link_issues may create (case-insensitive); empty = none. */
|
|
14
|
+
allowedLinkTypes: string[];
|
|
15
|
+
/** Issue type names jira_create_issue may create (case-insensitive); empty = creation off. */
|
|
16
|
+
creatableIssueTypes: string[];
|
|
17
|
+
/** Cap on issues one run may create in this project; null = no cap. Best-effort across resumed or concurrent attempts of one run. */
|
|
18
|
+
maxNewIssuesPerRun: number | null;
|
|
19
|
+
}
|
|
20
|
+
/** Issues one run has created in one project (by this process), plus creates still in flight. */
|
|
21
|
+
export interface RunCreationCounter {
|
|
22
|
+
fingerprinted: number;
|
|
23
|
+
unfingerprinted: number;
|
|
24
|
+
inFlight: number;
|
|
25
|
+
}
|
|
26
|
+
/** What jira_create_issue needs beyond the links: one per run attempt. */
|
|
27
|
+
export interface IssueCreationContext {
|
|
28
|
+
runId: string;
|
|
29
|
+
/** Keyed by project key. Owned by the run: every call in the run shares it. */
|
|
30
|
+
counters: Map<string, RunCreationCounter>;
|
|
31
|
+
/** fileIssue bound to the database. */
|
|
32
|
+
fileIssue: (input: FileIssueInput) => Promise<FileIssueResult>;
|
|
33
|
+
/** Fingerprint rows this run created in the project (durable, so it survives a resumed attempt). */
|
|
34
|
+
recordedCreates: (projectKey: string) => Promise<number>;
|
|
35
|
+
}
|
|
36
|
+
export interface IssueToolContext {
|
|
37
|
+
agentId: string;
|
|
38
|
+
links: readonly IssueProjectLink[];
|
|
39
|
+
trackers: IssueTrackerRegistry;
|
|
40
|
+
/** Live re-read of the link (null = unlinked since load). */
|
|
41
|
+
currentLink: (projectKey: string) => Promise<IssueProjectLink | null>;
|
|
42
|
+
/** Absent: jira_create_issue is refused. */
|
|
43
|
+
creation?: IssueCreationContext;
|
|
44
|
+
}
|
|
45
|
+
export declare const ISSUE_TRACKER_TOOL_DEFS: LoadedTool[];
|
|
46
|
+
export declare const ISSUE_TRACKER_TOOL_NAMES: ReadonlySet<string>;
|
|
47
|
+
/**
|
|
48
|
+
* The wardby-owned Jira issue property key for this agent's `property`.
|
|
49
|
+
* Agent ids are cuids (letters and digits, never a '.'), so the first '.'
|
|
50
|
+
* after `wardby.` always ends the agent id: no agent id plus property name
|
|
51
|
+
* can spell another agent's namespace.
|
|
52
|
+
*/
|
|
53
|
+
export declare const propertyKey: (agentId: string, property: string) => string;
|
|
54
|
+
export declare function scopeJql(projectKeys: readonly string[], jql: string): {
|
|
55
|
+
jql: string;
|
|
56
|
+
} | {
|
|
57
|
+
error: string;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Dispatches one built-in jira_* tool call. Never throws — mirrors
|
|
61
|
+
* runner.ts's `runSandboxTool` contract: a failure becomes a JSON error
|
|
62
|
+
* result fed back to the model as the tool's result.
|
|
63
|
+
*/
|
|
64
|
+
export declare function handleIssueTrackerTool(name: string, argsJson: string, ctx: IssueToolContext): Promise<string>;
|