@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.
Files changed (275) hide show
  1. package/.env.example +22 -3
  2. package/README.md +11 -9
  3. package/dist/cli-help.d.ts +1 -1
  4. package/dist/cli-help.js +1 -0
  5. package/dist/cli.js +68 -10
  6. package/dist/coding/base-commit.d.ts +6 -0
  7. package/dist/coding/base-commit.js +12 -0
  8. package/dist/coding/protocol.d.ts +17 -1
  9. package/dist/coding/protocol.js +17 -6
  10. package/dist/coding/provider.d.ts +6 -1
  11. package/dist/coding/provider.js +16 -9
  12. package/dist/config/providers.d.ts +25 -0
  13. package/dist/config/providers.js +71 -0
  14. package/dist/core/attribution.d.ts +101 -0
  15. package/dist/core/attribution.js +208 -0
  16. package/dist/core/budget-groups.d.ts +17 -10
  17. package/dist/core/budget-groups.js +15 -12
  18. package/dist/core/coding-queue.d.ts +3 -0
  19. package/dist/core/coding-queue.js +6 -2
  20. package/dist/core/coding-service-status.d.ts +11 -0
  21. package/dist/core/coding-service-status.js +17 -0
  22. package/dist/core/cost-report.d.ts +88 -0
  23. package/dist/core/cost-report.js +248 -0
  24. package/dist/core/dispatch.d.ts +42 -2
  25. package/dist/core/dispatch.js +144 -26
  26. package/dist/core/engine-native.js +17 -4
  27. package/dist/core/glob.d.ts +10 -0
  28. package/dist/core/glob.js +33 -0
  29. package/dist/core/host-events.d.ts +24 -1
  30. package/dist/core/host-events.js +341 -1
  31. package/dist/core/host-status.d.ts +19 -2
  32. package/dist/core/host-status.js +47 -30
  33. package/dist/core/issue-bridge.d.ts +60 -0
  34. package/dist/core/issue-bridge.js +189 -0
  35. package/dist/core/issue-dedupe.d.ts +70 -0
  36. package/dist/core/issue-dedupe.js +255 -0
  37. package/dist/core/issue-events.d.ts +42 -0
  38. package/dist/core/issue-events.js +155 -0
  39. package/dist/core/issue-status.d.ts +29 -0
  40. package/dist/core/issue-status.js +241 -0
  41. package/dist/core/issue-tracker-tools.d.ts +64 -0
  42. package/dist/core/issue-tracker-tools.js +850 -0
  43. package/dist/core/model-usage.d.ts +10 -0
  44. package/dist/core/model-usage.js +24 -0
  45. package/dist/core/reconciler.d.ts +8 -4
  46. package/dist/core/reconciler.js +15 -4
  47. package/dist/core/review-host-tools.js +10 -3
  48. package/dist/core/run-pricing.d.ts +61 -0
  49. package/dist/core/run-pricing.js +56 -0
  50. package/dist/core/runner.d.ts +5 -2
  51. package/dist/core/runner.js +178 -27
  52. package/dist/core/scheduler.d.ts +4 -1
  53. package/dist/core/scheduler.js +3 -2
  54. package/dist/core/self-defects.d.ts +80 -0
  55. package/dist/core/self-defects.js +180 -0
  56. package/dist/core/tool-names.js +3 -0
  57. package/dist/core/webhooks.d.ts +9 -1
  58. package/dist/core/webhooks.js +19 -1
  59. package/dist/env.js +6 -1
  60. package/dist/generated/prisma/browser.d.ts +66 -0
  61. package/dist/generated/prisma/client.d.ts +66 -0
  62. package/dist/generated/prisma/commonInputTypes.d.ts +122 -52
  63. package/dist/generated/prisma/enums.d.ts +7 -0
  64. package/dist/generated/prisma/enums.js +6 -0
  65. package/dist/generated/prisma/internal/class.d.ts +99 -0
  66. package/dist/generated/prisma/internal/class.js +4 -4
  67. package/dist/generated/prisma/internal/prismaNamespace.d.ts +826 -1
  68. package/dist/generated/prisma/internal/prismaNamespace.js +135 -2
  69. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +142 -0
  70. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +135 -2
  71. package/dist/generated/prisma/models/Agent.d.ts +389 -1
  72. package/dist/generated/prisma/models/AgentIssueProject.d.ts +1838 -0
  73. package/dist/generated/prisma/models/AgentIssueProject.js +1 -0
  74. package/dist/generated/prisma/models/AgentRepository.d.ts +1 -1
  75. package/dist/generated/prisma/models/AuthUser.d.ts +1 -1
  76. package/dist/generated/prisma/models/CodingProxySession.d.ts +73 -1
  77. package/dist/generated/prisma/models/CodingRun.d.ts +130 -1
  78. package/dist/generated/prisma/models/CodingRunServiceStatus.d.ts +1404 -0
  79. package/dist/generated/prisma/models/CodingRunServiceStatus.js +1 -0
  80. package/dist/generated/prisma/models/IssueFingerprint.d.ts +1183 -0
  81. package/dist/generated/prisma/models/IssueFingerprint.js +1 -0
  82. package/dist/generated/prisma/models/IssuePullRequest.d.ts +1255 -0
  83. package/dist/generated/prisma/models/IssuePullRequest.js +1 -0
  84. package/dist/generated/prisma/models/ModelCatalogEntry.d.ts +1322 -0
  85. package/dist/generated/prisma/models/ModelCatalogEntry.js +1 -0
  86. package/dist/generated/prisma/models/Run.d.ts +933 -1
  87. package/dist/generated/prisma/models/RunAttribution.d.ts +1259 -0
  88. package/dist/generated/prisma/models/RunAttribution.js +1 -0
  89. package/dist/generated/prisma/models/RunIssueStatus.d.ts +1199 -0
  90. package/dist/generated/prisma/models/RunIssueStatus.js +1 -0
  91. package/dist/generated/prisma/models/RunModelUsage.d.ts +1316 -0
  92. package/dist/generated/prisma/models/RunModelUsage.js +1 -0
  93. package/dist/generated/prisma/models/WorkItem.d.ts +1408 -0
  94. package/dist/generated/prisma/models/WorkItem.js +1 -0
  95. package/dist/generated/prisma/models.d.ts +9 -0
  96. package/dist/help-index.json +355 -16
  97. package/dist/import/neutral-schema.d.ts +16 -16
  98. package/dist/knowledge/check.d.ts +13 -0
  99. package/dist/knowledge/check.js +69 -0
  100. package/dist/knowledge/cli.d.ts +14 -0
  101. package/dist/knowledge/cli.js +67 -0
  102. package/dist/knowledge/concept.d.ts +54 -0
  103. package/dist/knowledge/concept.js +78 -0
  104. package/dist/knowledge/note.d.ts +11 -0
  105. package/dist/knowledge/note.js +39 -0
  106. package/dist/knowledge/relevance.d.ts +11 -0
  107. package/dist/knowledge/relevance.js +14 -0
  108. package/dist/knowledge/span-hash.d.ts +3 -0
  109. package/dist/knowledge/span-hash.js +16 -0
  110. package/dist/mcp/auth/access.d.ts +4 -2
  111. package/dist/mcp/auth/ownership.d.ts +9 -9
  112. package/dist/mcp/auth/resource-server.d.ts +3 -1
  113. package/dist/mcp/auth/resource-server.js +18 -3
  114. package/dist/mcp/auth/self-hosted/credentials.d.ts +3 -3
  115. package/dist/mcp/auth/self-hosted/session.d.ts +5 -5
  116. package/dist/mcp/context.d.ts +3 -0
  117. package/dist/mcp/host-events/deliveries.d.ts +9 -0
  118. package/dist/mcp/host-events/deliveries.js +17 -0
  119. package/dist/mcp/host-events/github-ingress.d.ts +4 -2
  120. package/dist/mcp/host-events/github-ingress.js +4 -13
  121. package/dist/mcp/host-events/jira-ingress.d.ts +29 -0
  122. package/dist/mcp/host-events/jira-ingress.js +92 -0
  123. package/dist/mcp/index.d.ts +2 -0
  124. package/dist/mcp/index.js +87 -9
  125. package/dist/mcp/server.js +5 -2
  126. package/dist/mcp/tools/agents.js +74 -3
  127. package/dist/mcp/tools/cost-report.d.ts +8 -0
  128. package/dist/mcp/tools/cost-report.js +60 -0
  129. package/dist/mcp/tools/issue-projects.d.ts +2 -0
  130. package/dist/mcp/tools/issue-projects.js +238 -0
  131. package/dist/mcp/tools/model-catalog.d.ts +22 -0
  132. package/dist/mcp/tools/model-catalog.js +423 -0
  133. package/dist/mcp/tools/repositories.js +2 -1
  134. package/dist/mcp/tools/tools.d.ts +2 -2
  135. package/dist/mcp/tools/trigger.js +33 -5
  136. package/dist/mcp/transport/streamable-http.d.ts +5 -0
  137. package/dist/mcp/transport/streamable-http.js +23 -1
  138. package/dist/mcp/webhooks/ingress.d.ts +2 -1
  139. package/dist/mcp/webhooks/ingress.js +9 -2
  140. package/dist/providers/auth/self-hosted.d.ts +8 -1
  141. package/dist/providers/auth/self-hosted.js +39 -2
  142. package/dist/providers/coding-proxy/memory-ledger.d.ts +1 -1
  143. package/dist/providers/coding-proxy/memory-ledger.js +10 -1
  144. package/dist/providers/coding-proxy/metering.d.ts +2 -1
  145. package/dist/providers/coding-proxy/metering.js +13 -2
  146. package/dist/providers/coding-proxy/prisma-ledger.js +59 -6
  147. package/dist/providers/coding-proxy/proxy.d.ts +12 -2
  148. package/dist/providers/coding-proxy/proxy.js +92 -30
  149. package/dist/providers/coding-proxy/types.d.ts +18 -1
  150. package/dist/providers/coding-proxy/types.js +12 -1
  151. package/dist/providers/engine/types.d.ts +19 -0
  152. package/dist/providers/executor/composition.js +9 -1
  153. package/dist/providers/executor/container.d.ts +30 -2
  154. package/dist/providers/executor/container.js +98 -17
  155. package/dist/providers/executor/dbos.d.ts +2 -0
  156. package/dist/providers/executor/dbos.js +7 -5
  157. package/dist/providers/executor/routing.d.ts +6 -0
  158. package/dist/providers/executor/routing.js +5 -0
  159. package/dist/providers/executor/types.d.ts +12 -0
  160. package/dist/providers/issue-tracker/adf.d.ts +31 -0
  161. package/dist/providers/issue-tracker/adf.js +181 -0
  162. package/dist/providers/issue-tracker/index.d.ts +5 -0
  163. package/dist/providers/issue-tracker/index.js +12 -0
  164. package/dist/providers/issue-tracker/jira-client.d.ts +41 -0
  165. package/dist/providers/issue-tracker/jira-client.js +151 -0
  166. package/dist/providers/issue-tracker/jira-events.d.ts +3 -0
  167. package/dist/providers/issue-tracker/jira-events.js +98 -0
  168. package/dist/providers/issue-tracker/jira.d.ts +116 -0
  169. package/dist/providers/issue-tracker/jira.js +502 -0
  170. package/dist/providers/issue-tracker/types.d.ts +269 -0
  171. package/dist/providers/issue-tracker/types.js +16 -0
  172. package/dist/providers/jobs/docker.d.ts +5 -1
  173. package/dist/providers/jobs/docker.js +61 -33
  174. package/dist/providers/jobs/kubernetes.d.ts +3 -0
  175. package/dist/providers/jobs/kubernetes.js +44 -4
  176. package/dist/providers/jobs/service-state.d.ts +22 -0
  177. package/dist/providers/jobs/service-state.js +17 -0
  178. package/dist/providers/llm/anthropic.d.ts +3 -3
  179. package/dist/providers/llm/anthropic.js +3 -9
  180. package/dist/providers/llm/bedrock.d.ts +3 -3
  181. package/dist/providers/llm/bedrock.js +3 -9
  182. package/dist/providers/llm/catalog-lookup.d.ts +10 -0
  183. package/dist/providers/llm/catalog-lookup.js +15 -0
  184. package/dist/providers/llm/catalog-shipped.d.ts +18 -0
  185. package/dist/providers/llm/catalog-shipped.js +197 -0
  186. package/dist/providers/llm/catalog-store.d.ts +58 -0
  187. package/dist/providers/llm/catalog-store.js +138 -0
  188. package/dist/providers/llm/catalog-types.d.ts +66 -0
  189. package/dist/providers/llm/catalog-types.js +64 -0
  190. package/dist/providers/llm/catalog.d.ts +61 -0
  191. package/dist/providers/llm/catalog.js +147 -0
  192. package/dist/providers/llm/claude-provider.d.ts +10 -14
  193. package/dist/providers/llm/claude-provider.js +11 -6
  194. package/dist/providers/llm/index.d.ts +9 -6
  195. package/dist/providers/llm/index.js +8 -5
  196. package/dist/providers/llm/openai.d.ts +14 -5
  197. package/dist/providers/llm/openai.js +24 -14
  198. package/dist/providers/llm/pricing-core.d.ts +5 -3
  199. package/dist/providers/llm/registration.js +8 -12
  200. package/dist/providers/llm/routing.d.ts +18 -17
  201. package/dist/providers/llm/routing.js +40 -24
  202. package/dist/providers/review-host/github-events.js +47 -1
  203. package/dist/providers/review-host/github.js +7 -6
  204. package/dist/providers/review-host/types.d.ts +25 -0
  205. package/dist/providers/vcs/git.js +2 -22
  206. package/dist/providers/vcs/github.d.ts +20 -0
  207. package/dist/providers/vcs/github.js +28 -2
  208. package/dist/providers/vcs/types.d.ts +6 -0
  209. package/dist/quickstart/index.d.ts +8 -0
  210. package/dist/quickstart/index.js +34 -34
  211. package/dist/serve.js +8 -2
  212. package/dist/viewer/api-schema.d.ts +2757 -0
  213. package/dist/viewer/api-schema.js +165 -0
  214. package/dist/viewer/build-schemas.d.ts +2 -0
  215. package/dist/viewer/build-schemas.js +18 -0
  216. package/dist/viewer/event-bus.d.ts +38 -0
  217. package/dist/viewer/event-bus.js +232 -0
  218. package/dist/viewer/graph.d.ts +40 -0
  219. package/dist/viewer/graph.js +243 -0
  220. package/dist/viewer/http.d.ts +30 -0
  221. package/dist/viewer/http.js +133 -0
  222. package/dist/viewer/run-detail.d.ts +4 -0
  223. package/dist/viewer/run-detail.js +61 -0
  224. package/dist/wardby-bin.js +5 -0
  225. package/docs/README.md +10 -0
  226. package/docs/agent-recipes.md +383 -0
  227. package/docs/code-review-agents.md +29 -2
  228. package/docs/coding-agent-setup.md +3 -0
  229. package/docs/coding-worker-isolation.md +39 -5
  230. package/docs/getting-started-gke.md +28 -11
  231. package/docs/getting-started-identity-provider.md +49 -38
  232. package/docs/getting-started.md +14 -0
  233. package/docs/jira-agents.md +649 -0
  234. package/docs/knowledge.md +387 -0
  235. package/docs/models.md +221 -0
  236. package/docs/security-deployment.md +19 -9
  237. package/docs/viewer-api.md +142 -0
  238. package/help/admin-viewer.md +39 -0
  239. package/help/agent-recipes.md +173 -0
  240. package/help/architecture-agent.md +189 -0
  241. package/help/builder-agent.md +80 -0
  242. package/help/code-review-agents.md +6 -0
  243. package/help/cost-attribution.md +67 -0
  244. package/help/creating-agents.md +22 -0
  245. package/help/deploy-gke.md +6 -0
  246. package/help/errors/model-unavailable.md +63 -0
  247. package/help/getting-started.md +1 -0
  248. package/help/github.md +18 -0
  249. package/help/identity-and-access.md +8 -3
  250. package/help/jira.md +135 -0
  251. package/help/knowledge.md +47 -0
  252. package/help/models.md +90 -0
  253. package/help/operating-agents.md +7 -1
  254. package/help/troubleshooting/budgets.md +6 -0
  255. package/package.json +5 -2
  256. package/prisma/migrations/20260930000000_jira_issue_projects/migration.sql +34 -0
  257. package/prisma/migrations/20261001000000_jira_phase2_allowlists/migration.sql +3 -0
  258. package/prisma/migrations/20261001010000_jira_link_types_allowlist/migration.sql +2 -0
  259. package/prisma/migrations/20261002000000_jira_coding_bridge/migration.sql +28 -0
  260. package/prisma/migrations/20261002010000_jira_issue_creation/migration.sql +25 -0
  261. package/prisma/migrations/20261003000000_issue_cost_attribution/migration.sql +56 -0
  262. package/prisma/migrations/20261003010000_coding_run_service_status/migration.sql +23 -0
  263. package/prisma/migrations/20261003020000_viewer_notify/migration.sql +54 -0
  264. package/prisma/migrations/20261003030000_viewer_notify_fixes/migration.sql +47 -0
  265. package/prisma/migrations/20261003040000_viewer_indexes/migration.sql +12 -0
  266. package/prisma/migrations/20261004000000_model_catalog/migration.sql +26 -0
  267. package/prisma/schema.prisma +258 -2
  268. package/dist/mcp/tools/models.d.ts +0 -8
  269. package/dist/mcp/tools/models.js +0 -15
  270. package/dist/providers/llm/pricing-anthropic.d.ts +0 -14
  271. package/dist/providers/llm/pricing-anthropic.js +0 -48
  272. package/dist/providers/llm/pricing-bedrock-claude.d.ts +0 -20
  273. package/dist/providers/llm/pricing-bedrock-claude.js +0 -46
  274. package/dist/providers/llm/pricing.d.ts +0 -30
  275. package/dist/providers/llm/pricing.js +0 -74
@@ -9,6 +9,7 @@
9
9
  import type { Agent } from "#prisma";
10
10
  import type { Executor } from "../providers/executor/types.js";
11
11
  import { type DispatchDb } from "./dispatch.js";
12
+ import type { SelfDefectSink } from "./self-defects.js";
12
13
  export type SchedulerDb = DispatchDb;
13
14
  /**
14
15
  * Per spec: "Executor throws synchronously on start → the run is marked
@@ -25,7 +26,7 @@ export declare function findDueCandidates(db: Pick<SchedulerDb, "agent">, now: D
25
26
  * Returns the new run's id, or null if the agent wasn't (or is no longer)
26
27
  * due, or another tick already holds its row lock.
27
28
  */
28
- export declare function claimDueRun(db: SchedulerDb, executor: Executor, agentId: string, now: Date): Promise<string | null>;
29
+ export declare function claimDueRun(db: SchedulerDb, executor: Executor, agentId: string, now: Date, selfDefects?: SelfDefectSink): Promise<string | null>;
29
30
  export interface SchedulerOptions {
30
31
  executor: Executor;
31
32
  db?: SchedulerDb;
@@ -39,6 +40,8 @@ export interface SchedulerOptions {
39
40
  * failing hook cannot stop scheduled agents from firing.
40
41
  */
41
42
  onLeaderTick?: () => Promise<void>;
43
+ /** Files a self-defect when a scheduled run's executor fails to start it. Optional. */
44
+ selfDefects?: SelfDefectSink;
42
45
  }
43
46
  export interface SchedulerHandle {
44
47
  stop(): void;
@@ -39,11 +39,12 @@ export async function findDueCandidates(db, now) {
39
39
  * Returns the new run's id, or null if the agent wasn't (or is no longer)
40
40
  * due, or another tick already holds its row lock.
41
41
  */
42
- export async function claimDueRun(db, executor, agentId, now) {
42
+ export async function claimDueRun(db, executor, agentId, now, selfDefects) {
43
43
  const dispatched = await dispatchRun({
44
44
  db,
45
45
  executor,
46
46
  agentId,
47
+ selfDefects,
47
48
  trigger: "scheduled",
48
49
  now,
49
50
  lockAgent: true,
@@ -88,7 +89,7 @@ export function startScheduler(options) {
88
89
  const due = await findDueCandidates(db, now());
89
90
  for (const agent of due) {
90
91
  try {
91
- const runId = await claimDueRun(db, options.executor, agent.id, now());
92
+ const runId = await claimDueRun(db, options.executor, agent.id, now(), options.selfDefects);
92
93
  if (runId) {
93
94
  log(`firing agent "${agent.name}" -> run ${runId}`);
94
95
  }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Self-defects: when a run of an opted-in agent (Agent.defectProjectKey +
3
+ * defectIssueType) ends failed, lost or budget_exhausted, the control plane
4
+ * (no model involved) files a defect for it through fileIssue, deduped by a
5
+ * structural fingerprint so a recurring failure becomes "seen again" comments
6
+ * on one issue rather than a new issue per run.
7
+ *
8
+ * fingerprint self:<agentId>:<status>:<category>
9
+ * category CodingRun.failureCategory, else the leading `code:` token of
10
+ * Run.error, else "unknown" — only ever a closed-shape token
11
+ * (lower-case letters/underscore), never the error's free text
12
+ *
13
+ * Fails closed: the agent needs a live write link to defectProjectKey whose
14
+ * creatableIssueTypes includes defectIssueType; otherwise it logs and skips.
15
+ * Self-defects are not counted against the link's maxNewIssuesPerRun: that cap
16
+ * bounds what a run's model may create, while this is a control-plane action
17
+ * whose volume the fingerprint dedupe already bounds (one open issue per
18
+ * agent/status/category).
19
+ *
20
+ * Filed by whichever write made the run's row terminal (its update count says so, so a run files once): the
21
+ * runner, the container store, the reconciler, the coding queue's timeout, a failed executor start
22
+ * (markRunFailedFromExecutorError) and DBOS recovery's mark-failed.
23
+ *
24
+ * Best effort and never throws. No recursion: this is only ever called after a
25
+ * run's row is final, outside any run, and a failure to file only logs — it
26
+ * never fails a run, so it can never trigger another filing.
27
+ */
28
+ import type { PrismaClient } from "#prisma";
29
+ import { type IssueTrackerRegistry } from "../providers/issue-tracker/types.js";
30
+ import { type FileIssueInput, type FileIssueResult } from "./issue-dedupe.js";
31
+ /** Run statuses that file a defect; succeeded, cancelled and refused never do. */
32
+ export declare const SELF_DEFECT_STATUSES: ReadonlySet<string>;
33
+ /** How long a caller waits for the filing; past this it carries on in the background (and still only logs). */
34
+ export declare const SELF_DEFECT_WAIT_MS = 15000;
35
+ export type SelfDefectDb = Pick<PrismaClient, "agent" | "agentIssueProject" | "codingRun" | "$transaction">;
36
+ /** The finished run, as its final row has it. */
37
+ export interface SelfDefectRun {
38
+ id: string;
39
+ agentId: string;
40
+ status: string;
41
+ error: string | null;
42
+ finishedAt: Date | null;
43
+ }
44
+ export interface SelfDefectOptions {
45
+ /** Injected for tests; defaults to issue-dedupe's fileIssue on `db`. */
46
+ fileIssue?: (input: FileIssueInput) => Promise<FileIssueResult>;
47
+ waitMs?: number;
48
+ }
49
+ /**
50
+ * Where a write that ends a run files its self-defect: the database to read the run and file through, and the
51
+ * configured issue trackers. Absent, or with no tracker configured, the write files nothing and makes no extra query.
52
+ */
53
+ export interface SelfDefectSink {
54
+ db: SelfDefectDb & Pick<PrismaClient, "run">;
55
+ issueTrackers?: IssueTrackerRegistry;
56
+ /** Injected for tests; the wait defaults to SELF_DEFECT_SHORT_WAIT_MS. */
57
+ options?: SelfDefectOptions;
58
+ }
59
+ /** The bound for callers on a dispatch, queue or executor path: past it the filing carries on detached. */
60
+ export declare const SELF_DEFECT_SHORT_WAIT_MS = 2000;
61
+ /** True when at least one issue tracker is configured (no Jira site configured yields an empty registry). */
62
+ export declare function hasIssueTrackers(trackers: IssueTrackerRegistry | undefined): trackers is IssueTrackerRegistry;
63
+ /**
64
+ * The leading `code:` token of a run error (lower-cased) when it is a safe category (CATEGORY_PATTERN), else
65
+ * "unknown". Never any of the free text after it.
66
+ */
67
+ export declare function errorClass(error: string | null | undefined): string;
68
+ export declare function selfDefectFingerprint(agentId: string, status: string, category: string): string;
69
+ /**
70
+ * Files (or re-sights) the defect for a finished run. Resolves to the filing's
71
+ * result, or null when nothing was filed (not a filing status, not opted in,
72
+ * no live link, an error, or still in flight after `waitMs`). Never throws.
73
+ */
74
+ export declare function fileSelfDefect(db: SelfDefectDb, trackers: IssueTrackerRegistry | undefined, run: SelfDefectRun, opts?: SelfDefectOptions): Promise<FileIssueResult | null>;
75
+ /**
76
+ * For a write that just made `runId` terminal (its update count said so, so this files once): reads the final row
77
+ * and files its self-defect, waiting at most SELF_DEFECT_SHORT_WAIT_MS. No-op, with no query, without a configured
78
+ * tracker. Never throws.
79
+ */
80
+ export declare function fileSelfDefectForRun(sink: SelfDefectSink | undefined, runId: string): Promise<FileIssueResult | null>;
@@ -0,0 +1,180 @@
1
+ import { agentFooter } from "../providers/issue-tracker/jira.js";
2
+ import { ISSUE_TRACKER_PROVIDERS, } from "../providers/issue-tracker/types.js";
3
+ import { fileIssue } from "./issue-dedupe.js";
4
+ import { propertyKey } from "./issue-tracker-tools.js";
5
+ import { logger } from "./logger.js";
6
+ const log = logger.child({ module: "self-defects" });
7
+ /** Run statuses that file a defect; succeeded, cancelled and refused never do. */
8
+ export const SELF_DEFECT_STATUSES = new Set(["failed", "lost", "budget_exhausted"]);
9
+ /** How long a caller waits for the filing; past this it carries on in the background (and still only logs). */
10
+ export const SELF_DEFECT_WAIT_MS = 15_000;
11
+ const SUMMARY_MAX_LENGTH = 255;
12
+ /**
13
+ * What a category may look like to be published in the issue and fingerprint: lower-case letters and underscores,
14
+ * at most 32. No digits, dots or dashes, so secret-shaped (ghp_…, sk-ant-…), host-shaped (db.internal.corp) and
15
+ * id-shaped (run-8f3a…) prefixes never reach Jira and fingerprints stay a small closed set.
16
+ */
17
+ const CATEGORY_PATTERN = /^[a-z][a-z_]{0,31}$/;
18
+ /** The bound for callers on a dispatch, queue or executor path: past it the filing carries on detached. */
19
+ export const SELF_DEFECT_SHORT_WAIT_MS = 2_000;
20
+ /** True when at least one issue tracker is configured (no Jira site configured yields an empty registry). */
21
+ export function hasIssueTrackers(trackers) {
22
+ return !!trackers && Object.values(trackers).some(Boolean);
23
+ }
24
+ /**
25
+ * The leading `code:` token of a run error (lower-cased) when it is a safe category (CATEGORY_PATTERN), else
26
+ * "unknown". Never any of the free text after it.
27
+ */
28
+ export function errorClass(error) {
29
+ const match = /^([^:\s]{1,64}):/.exec(error ?? "");
30
+ return safeCategory(match?.[1]) ?? "unknown";
31
+ }
32
+ export function selfDefectFingerprint(agentId, status, category) {
33
+ return `self:${agentId}:${status}:${category}`;
34
+ }
35
+ function safeCategory(value) {
36
+ const lowered = value?.trim().toLowerCase();
37
+ return lowered && CATEGORY_PATTERN.test(lowered) ? lowered : null;
38
+ }
39
+ /**
40
+ * Files (or re-sights) the defect for a finished run. Resolves to the filing's
41
+ * result, or null when nothing was filed (not a filing status, not opted in,
42
+ * no live link, an error, or still in flight after `waitMs`). Never throws.
43
+ */
44
+ export async function fileSelfDefect(db, trackers, run, opts = {}) {
45
+ if (!SELF_DEFECT_STATUSES.has(run.status) || !trackers)
46
+ return null;
47
+ const filing = attempt(db, trackers, run, opts).catch((err) => {
48
+ log.warn({ err, runId: run.id, agentId: run.agentId }, "could not file the run's self-defect");
49
+ return null;
50
+ });
51
+ const waitMs = opts.waitMs ?? SELF_DEFECT_WAIT_MS;
52
+ let timer;
53
+ const timedOut = new Promise((resolve) => {
54
+ timer = setTimeout(() => {
55
+ log.warn({ runId: run.id, waitMs }, "self-defect filing still in progress; no longer waiting for it");
56
+ resolve(null);
57
+ }, waitMs);
58
+ timer.unref?.();
59
+ });
60
+ try {
61
+ return await Promise.race([filing, timedOut]);
62
+ }
63
+ finally {
64
+ clearTimeout(timer);
65
+ }
66
+ }
67
+ /**
68
+ * For a write that just made `runId` terminal (its update count said so, so this files once): reads the final row
69
+ * and files its self-defect, waiting at most SELF_DEFECT_SHORT_WAIT_MS. No-op, with no query, without a configured
70
+ * tracker. Never throws.
71
+ */
72
+ export async function fileSelfDefectForRun(sink, runId) {
73
+ if (!sink || !hasIssueTrackers(sink.issueTrackers))
74
+ return null;
75
+ let row;
76
+ try {
77
+ row = await sink.db.run.findUnique({
78
+ where: { id: runId },
79
+ select: { id: true, agentId: true, status: true, error: true, finishedAt: true },
80
+ });
81
+ }
82
+ catch (err) {
83
+ log.warn({ err, runId }, "could not read the run to file its self-defect");
84
+ return null;
85
+ }
86
+ if (!row)
87
+ return null;
88
+ return fileSelfDefect(sink.db, sink.issueTrackers, row, {
89
+ waitMs: SELF_DEFECT_SHORT_WAIT_MS,
90
+ ...sink.options,
91
+ });
92
+ }
93
+ async function attempt(db, trackers, run, opts) {
94
+ const configured = ISSUE_TRACKER_PROVIDERS.filter((p) => trackers[p]);
95
+ if (configured.length === 0)
96
+ return null;
97
+ const agent = await db.agent.findUnique({
98
+ where: { id: run.agentId },
99
+ select: { id: true, name: true, defectProjectKey: true, defectIssueType: true },
100
+ });
101
+ if (!agent?.defectProjectKey || !agent.defectIssueType)
102
+ return null;
103
+ const projectKey = agent.defectProjectKey;
104
+ const issueType = agent.defectIssueType;
105
+ let tracker;
106
+ let link;
107
+ for (const provider of configured) {
108
+ // Live, read now: an unlink, downgrade or allowlist change since the run started applies here.
109
+ const row = await db.agentIssueProject.findUnique({
110
+ where: { agentId_provider_projectKey: { agentId: agent.id, provider, projectKey } },
111
+ select: {
112
+ provider: true,
113
+ projectKey: true,
114
+ access: true,
115
+ commentVisibilityRole: true,
116
+ creatableIssueTypes: true,
117
+ },
118
+ });
119
+ if (!row)
120
+ continue;
121
+ const typeAllowed = row.creatableIssueTypes.some((t) => t.trim().toLowerCase() === issueType.trim().toLowerCase());
122
+ if (row.access !== "write" || !typeAllowed) {
123
+ log.warn({ runId: run.id, agentId: agent.id, projectKey, access: row.access, typeAllowed }, "self-defects skipped: the agent's link to the defect project is not writable or does not allow the issue type");
124
+ return null;
125
+ }
126
+ tracker = trackers[provider];
127
+ link = row;
128
+ break;
129
+ }
130
+ if (!tracker || !link) {
131
+ log.warn({ runId: run.id, agentId: agent.id, projectKey }, "self-defects skipped: no link to the defect project");
132
+ return null;
133
+ }
134
+ let codingCategory = null;
135
+ try {
136
+ const coding = await db.codingRun.findUnique({ where: { runId: run.id }, select: { failureCategory: true } });
137
+ codingCategory = safeCategory(coding?.failureCategory);
138
+ }
139
+ catch (err) {
140
+ log.debug({ err, runId: run.id }, "no coding failure category for the self-defect");
141
+ }
142
+ const category = codingCategory ?? errorClass(run.error);
143
+ const finished = (run.finishedAt ?? new Date()).toISOString();
144
+ const summary = `wardby agent "${agent.name}": ${run.status}${category === "unknown" ? "" : ` (${category})`}`;
145
+ const description = [
146
+ `A run of wardby agent "${agent.name}" ended ${run.status}.`,
147
+ "",
148
+ `- Run: ${run.id}`,
149
+ `- Agent: ${agent.id}`,
150
+ `- Status: ${run.status}`,
151
+ `- Failure category: ${category}`,
152
+ `- Finished: ${finished}`,
153
+ "",
154
+ "Filed by wardby itself. The run's error text is not included; see the run in wardby for details.",
155
+ ].join("\n");
156
+ const file = opts.fileIssue ?? ((input) => fileIssue({ db }, input));
157
+ // Not subject to maxNewIssuesPerRun (control plane; dedupe bounds volume), so createAllowed stays at its default.
158
+ const result = await file({
159
+ agentId: agent.id,
160
+ runId: run.id,
161
+ link,
162
+ tracker,
163
+ fingerprint: selfDefectFingerprint(agent.id, run.status, category),
164
+ create: {
165
+ issueType,
166
+ summary: summary.length > SUMMARY_MAX_LENGTH ? `${summary.slice(0, SUMMARY_MAX_LENGTH - 1)}…` : summary,
167
+ descriptionMarkdown: description,
168
+ properties: { [propertyKey(agent.id, "self-defect")]: { runId: run.id } },
169
+ },
170
+ footerMarkdown: agentFooter(agent.id),
171
+ seenAgainMarkdown: `Run ${run.id} ended ${run.status} at ${finished}.\n\n${agentFooter(agent.id)}`,
172
+ });
173
+ if ("error" in result) {
174
+ log.warn({ runId: run.id, agentId: agent.id, error: result.error }, "self-defect filing failed");
175
+ }
176
+ else {
177
+ log.info({ runId: run.id, agentId: agent.id, outcome: result.outcome, issueKey: result.issueKey }, "self-defect filed");
178
+ }
179
+ return result;
180
+ }
@@ -1,5 +1,6 @@
1
1
  import { MEMORY_TOOL_NAMES } from "./memory-tools.js";
2
2
  import { SUBAGENT_MEMORY_TOOL_NAMES } from "./subagent-memory-tools.js";
3
+ import { ISSUE_TRACKER_TOOL_NAMES } from "./issue-tracker-tools.js";
3
4
  /** The runner's synthesized sub-agent tools are `delegate_to_<boundName>`. */
4
5
  export const DELEGATE_TOOL_PREFIX = "delegate_to_";
5
6
  /**
@@ -13,6 +14,8 @@ export function reservedToolNameReason(name) {
13
14
  if (SUBAGENT_MEMORY_TOOL_NAMES.has(name)) {
14
15
  return `Tool name "${name}" is reserved for the built-in sub-agent memory tools.`;
15
16
  }
17
+ if (ISSUE_TRACKER_TOOL_NAMES.has(name))
18
+ return `Tool name "${name}" is reserved for the built-in Jira tools.`;
16
19
  if (name.startsWith(DELEGATE_TOOL_PREFIX)) {
17
20
  return `Tool names starting with "${DELEGATE_TOOL_PREFIX}" are reserved for sub-agent delegation tools.`;
18
21
  }
@@ -1,5 +1,6 @@
1
1
  import type { PrismaClient, Webhook } from "#prisma";
2
2
  import type { Executor } from "../providers/executor/types.js";
3
+ import type { IssueTrackerRegistry } from "../providers/issue-tracker/types.js";
3
4
  export type WebhookMetadata = Pick<Webhook, "id" | "agentId" | "status" | "ownerId" | "createdAt" | "lastFiredAt">;
4
5
  /** Creates a webhook and returns the raw secret ONCE — only its hash is ever stored. */
5
6
  export declare function createWebhook(agentId: string, ownerId: string, db: PrismaClient): Promise<{
@@ -21,10 +22,17 @@ export type ResolveWebhookRunResult = {
21
22
  } | {
22
23
  ok: false;
23
24
  reason: "not_found" | "invalid_secret" | "disabled";
25
+ } | {
26
+ ok: false;
27
+ reason: "invalid_issue";
28
+ message: string;
24
29
  };
25
30
  /**
26
31
  * Validates the presented secret and, if valid + enabled, enqueues a run via
27
32
  * the exact same `createRun` path `trigger_agent`/the scheduler use — the
28
33
  * budget guardrail applies identically regardless of what triggered the run.
29
34
  */
30
- export declare function resolveWebhookRun(id: string, presentedSecret: string, db: PrismaClient, executor: Executor, codingTask?: string): Promise<ResolveWebhookRunResult>;
35
+ export declare function resolveWebhookRun(id: string, presentedSecret: string, db: PrismaClient, executor: Executor, codingTask?: string, opts?: {
36
+ issue?: unknown;
37
+ issueTrackers?: IssueTrackerRegistry;
38
+ }): Promise<ResolveWebhookRunResult>;
@@ -9,6 +9,7 @@
9
9
  * is always 32 bytes, so there's no length-mismatch case to special-case).
10
10
  */
11
11
  import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
12
+ import { AttributionError, explicitAttribution, RESPONSE_PATH_SNAPSHOT_BUDGET, } from "./attribution.js";
12
13
  import { dispatchRun } from "./dispatch.js";
13
14
  import { atLeast, effectiveAccess } from "./grants.js";
14
15
  function hashSecret(secret) {
@@ -66,7 +67,7 @@ export async function deleteWebhook(id, db) {
66
67
  * the exact same `createRun` path `trigger_agent`/the scheduler use — the
67
68
  * budget guardrail applies identically regardless of what triggered the run.
68
69
  */
69
- export async function resolveWebhookRun(id, presentedSecret, db, executor, codingTask) {
70
+ export async function resolveWebhookRun(id, presentedSecret, db, executor, codingTask, opts = {}) {
70
71
  const webhook = await db.webhook.findUnique({ where: { id } });
71
72
  if (!webhook)
72
73
  return { ok: false, reason: "not_found" };
@@ -80,12 +81,29 @@ export async function resolveWebhookRun(id, presentedSecret, db, executor, codin
80
81
  if (codingTask !== undefined && !taskAllowed(agent.kind, agent.codingProfile)) {
81
82
  return { ok: false, reason: "disabled" };
82
83
  }
84
+ // An invalid or unlinked issue refuses the call before any run exists.
85
+ let attribution;
86
+ if (opts.issue !== undefined) {
87
+ try {
88
+ attribution = await explicitAttribution(db, opts.issueTrackers, agent.id, opts.issue, {
89
+ ...RESPONSE_PATH_SNAPSHOT_BUDGET,
90
+ field: "wardbyIssue",
91
+ });
92
+ }
93
+ catch (err) {
94
+ if (err instanceof AttributionError)
95
+ return { ok: false, reason: "invalid_issue", message: err.message };
96
+ throw err;
97
+ }
98
+ }
83
99
  let rejected = "disabled";
84
100
  const dispatched = await dispatchRun({
85
101
  db,
86
102
  executor,
103
+ selfDefects: { db, issueTrackers: opts.issueTrackers },
87
104
  agentId: agent.id,
88
105
  trigger: "webhook",
106
+ attribution,
89
107
  // Coding agents keep the existing per-agent opt-in (allowWebhookTaskOverride);
90
108
  // a native agent's system prompt has no per-run input at all otherwise, so
91
109
  // accepting task text over an authenticated webhook call needs no separate
package/dist/env.js CHANGED
@@ -17,4 +17,9 @@ const projectDir = resolve(process.env.WARDBY_PROJECT_DIR || process.cwd());
17
17
  // process variable, preserving explicit shell/container configuration as the
18
18
  // highest-priority source in either mode.
19
19
  dotenvFlow.config({ path: join(projectDir, ".wardby"), silent: true });
20
- dotenvFlow.config({ path: projectDir });
20
+ // The application's own .env files are optional: dotenv-flow reports "no .env*
21
+ // files" as a failure, so only load when some exist. Not `silent: true`, which
22
+ // would also hide parse errors in a real .env.
23
+ if (dotenvFlow.listFiles({ path: projectDir, node_env: process.env.NODE_ENV }).length > 0) {
24
+ dotenvFlow.config({ path: projectDir });
25
+ }
@@ -122,6 +122,18 @@ export type CodingRun = Prisma.CodingRunModel;
122
122
  * "external" is reserved.
123
123
  */
124
124
  export type CodingService = Prisma.CodingServiceModel;
125
+ /**
126
+ * Model CodingRunServiceStatus
127
+ * Live start-up state of one coding-run service (one row per run x
128
+ * service), written by the job launcher as it observes the service, for
129
+ * display only (the viewer). The run's CodingRun.services
130
+ * snapshot says what was started; this says how far each one got.
131
+ * `attempts` is the readiness probes made so far, known only where wardby
132
+ * runs the probe itself (Docker); `reason` is a fixed identifier
133
+ * (image_unavailable, start_failed, exited, probe_failed, timed_out),
134
+ * never probe output.
135
+ */
136
+ export type CodingRunServiceStatus = Prisma.CodingRunServiceStatusModel;
125
137
  /**
126
138
  * Model CodingProxySession
127
139
  * Trusted-boundary state for one coding run. Only a capability digest and an
@@ -326,3 +338,57 @@ export type HostIdentity = Prisma.HostIdentityModel;
326
338
  * Lives 10 minutes.
327
339
  */
328
340
  export type HostIdentityLinkRequest = Prisma.HostIdentityLinkRequestModel;
341
+ /**
342
+ * Model AgentIssueProject
343
+ * Which issue-tracker projects (Jira today) a native agent may act on, and
344
+ * which issue events trigger it. Admin-approved in phase 1 (no way to verify
345
+ * an owner's Jira access). See
346
+ * docs/private/2026-09-30-jira-issue-tracker-design.md §3.
347
+ */
348
+ export type AgentIssueProject = Prisma.AgentIssueProjectModel;
349
+ /**
350
+ * Model IssueFingerprint
351
+ * Dedupe record: an issue wardby created for a fingerprint (several over time — regressions).
352
+ */
353
+ export type IssueFingerprint = Prisma.IssueFingerprintModel;
354
+ /**
355
+ * Model IssuePullRequest
356
+ * A pull request wardby opened for a tracker issue (the Jira -> code bridge). Provider-neutral on both sides.
357
+ * Deliberately no foreign keys to Agent or Run: audit-style rows that survive agent/run deletion.
358
+ */
359
+ export type IssuePullRequest = Prisma.IssuePullRequestModel;
360
+ /**
361
+ * Model RunIssueStatus
362
+ * The status comment wardby posts on the Jira issue whose event started this
363
+ * run ("working on it"), edited with the outcome when the run ends.
364
+ */
365
+ export type RunIssueStatus = Prisma.RunIssueStatusModel;
366
+ /**
367
+ * Model WorkItem
368
+ * Latest known state of an issue/epic in an external tracker (Jira, later
369
+ * Linear). Shared by every run attributed to it; titles are refreshed by the
370
+ * next snapshot, so reports always show current names.
371
+ */
372
+ export type WorkItem = Prisma.WorkItemModel;
373
+ /**
374
+ * Model RunAttribution
375
+ * Which work item a run's cost belongs to, with its parent frozen as of
376
+ * dispatch. Written in dispatchRun's persist transaction; child runs copy
377
+ * their parent's row (source "inherited"). Control-plane data only.
378
+ */
379
+ export type RunAttribution = Prisma.RunAttributionModel;
380
+ /**
381
+ * Model RunModelUsage
382
+ * Per-run, per-model usage by priced token kind. Sum of costUsd over a
383
+ * run's rows equals Run.costUsd (to Run.costUsd's 6 decimal places).
384
+ */
385
+ export type RunModelUsage = Prisma.RunModelUsageModel;
386
+ /**
387
+ * Model ModelCatalogEntry
388
+ * A deployment's override of the shipped model catalog
389
+ * (src/providers/llm/catalog-shipped.ts), written by set_model /
390
+ * disable_model (models:admin). A row fully replaces the shipped entry with
391
+ * the same key; enabled=false removes the model; a row with no shipped match
392
+ * adds one. Rates are the provider's exact published numbers (sourceUrl).
393
+ */
394
+ export type ModelCatalogEntry = Prisma.ModelCatalogEntryModel;
@@ -141,6 +141,18 @@ export type CodingRun = Prisma.CodingRunModel;
141
141
  * "external" is reserved.
142
142
  */
143
143
  export type CodingService = Prisma.CodingServiceModel;
144
+ /**
145
+ * Model CodingRunServiceStatus
146
+ * Live start-up state of one coding-run service (one row per run x
147
+ * service), written by the job launcher as it observes the service, for
148
+ * display only (the viewer). The run's CodingRun.services
149
+ * snapshot says what was started; this says how far each one got.
150
+ * `attempts` is the readiness probes made so far, known only where wardby
151
+ * runs the probe itself (Docker); `reason` is a fixed identifier
152
+ * (image_unavailable, start_failed, exited, probe_failed, timed_out),
153
+ * never probe output.
154
+ */
155
+ export type CodingRunServiceStatus = Prisma.CodingRunServiceStatusModel;
144
156
  /**
145
157
  * Model CodingProxySession
146
158
  * Trusted-boundary state for one coding run. Only a capability digest and an
@@ -345,3 +357,57 @@ export type HostIdentity = Prisma.HostIdentityModel;
345
357
  * Lives 10 minutes.
346
358
  */
347
359
  export type HostIdentityLinkRequest = Prisma.HostIdentityLinkRequestModel;
360
+ /**
361
+ * Model AgentIssueProject
362
+ * Which issue-tracker projects (Jira today) a native agent may act on, and
363
+ * which issue events trigger it. Admin-approved in phase 1 (no way to verify
364
+ * an owner's Jira access). See
365
+ * docs/private/2026-09-30-jira-issue-tracker-design.md §3.
366
+ */
367
+ export type AgentIssueProject = Prisma.AgentIssueProjectModel;
368
+ /**
369
+ * Model IssueFingerprint
370
+ * Dedupe record: an issue wardby created for a fingerprint (several over time — regressions).
371
+ */
372
+ export type IssueFingerprint = Prisma.IssueFingerprintModel;
373
+ /**
374
+ * Model IssuePullRequest
375
+ * A pull request wardby opened for a tracker issue (the Jira -> code bridge). Provider-neutral on both sides.
376
+ * Deliberately no foreign keys to Agent or Run: audit-style rows that survive agent/run deletion.
377
+ */
378
+ export type IssuePullRequest = Prisma.IssuePullRequestModel;
379
+ /**
380
+ * Model RunIssueStatus
381
+ * The status comment wardby posts on the Jira issue whose event started this
382
+ * run ("working on it"), edited with the outcome when the run ends.
383
+ */
384
+ export type RunIssueStatus = Prisma.RunIssueStatusModel;
385
+ /**
386
+ * Model WorkItem
387
+ * Latest known state of an issue/epic in an external tracker (Jira, later
388
+ * Linear). Shared by every run attributed to it; titles are refreshed by the
389
+ * next snapshot, so reports always show current names.
390
+ */
391
+ export type WorkItem = Prisma.WorkItemModel;
392
+ /**
393
+ * Model RunAttribution
394
+ * Which work item a run's cost belongs to, with its parent frozen as of
395
+ * dispatch. Written in dispatchRun's persist transaction; child runs copy
396
+ * their parent's row (source "inherited"). Control-plane data only.
397
+ */
398
+ export type RunAttribution = Prisma.RunAttributionModel;
399
+ /**
400
+ * Model RunModelUsage
401
+ * Per-run, per-model usage by priced token kind. Sum of costUsd over a
402
+ * run's rows equals Run.costUsd (to Run.costUsd's 6 decimal places).
403
+ */
404
+ export type RunModelUsage = Prisma.RunModelUsageModel;
405
+ /**
406
+ * Model ModelCatalogEntry
407
+ * A deployment's override of the shipped model catalog
408
+ * (src/providers/llm/catalog-shipped.ts), written by set_model /
409
+ * disable_model (models:admin). A row fully replaces the shipped entry with
410
+ * the same key; enabled=false removes the model; a row with no shipped match
411
+ * adds one. Rates are the provider's exact published numbers (sourceUrl).
412
+ */
413
+ export type ModelCatalogEntry = Prisma.ModelCatalogEntryModel;