@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
package/.env.example
CHANGED
|
@@ -11,6 +11,9 @@ OPENAI_API_KEY=
|
|
|
11
11
|
# Set LLM_PROVIDER=anthropic to select the Claude adapter (default: openai).
|
|
12
12
|
LLM_PROVIDER=openai
|
|
13
13
|
ANTHROPIC_API_KEY=
|
|
14
|
+
# How often (seconds) each process re-reads the model catalog from the database,
|
|
15
|
+
# so another process's set_model/disable_model/reset_model reaches it (docs/models.md).
|
|
16
|
+
# WARDBY_MODEL_CATALOG_REFRESH_SECONDS=45
|
|
14
17
|
|
|
15
18
|
# Phase 4: MCP server (`wardby mcp`). stdio is the local/no-OAuth default.
|
|
16
19
|
MCP_TRANSPORT=stdio
|
|
@@ -34,9 +37,9 @@ AUTH_ISSUER=
|
|
|
34
37
|
AUTH_JWKS_URI=
|
|
35
38
|
AUTH_AUDIENCE=
|
|
36
39
|
# Optional, delegating mode: wardby roles from a signed access-token claim.
|
|
37
|
-
# Privileged scopes (agents:admin, packages:approve, services:manage
|
|
38
|
-
# role granting them (admin: all
|
|
39
|
-
# service-manager: services:manage). Set
|
|
40
|
+
# Privileged scopes (agents:admin, packages:approve, services:manage, admin:view, models:admin)
|
|
41
|
+
# are honoured only for a role granting them (admin: all five; package-approver: packages:approve;
|
|
42
|
+
# service-manager: services:manage; model-manager: models:admin). Set
|
|
40
43
|
# both, or neither (then nobody has a role). The claim is an exact top-level
|
|
41
44
|
# name first (e.g. groups, roles, or a namespaced https://… claim), else a
|
|
42
45
|
# dotted path (e.g. realm_access.roles). The map is idpValue=wardbyRole pairs,
|
|
@@ -140,6 +143,22 @@ GITHUB_APP_WEBHOOK_SECRET=
|
|
|
140
143
|
GITHUB_APP_CLIENT_ID=
|
|
141
144
|
GITHUB_APP_CLIENT_SECRET=
|
|
142
145
|
|
|
146
|
+
# --- Jira Cloud (optional; see docs/jira-agents.md) ---
|
|
147
|
+
# wardby acts in Jira as an Atlassian service account, never a person's account:
|
|
148
|
+
# every comment and change an agent makes is attributed to this account.
|
|
149
|
+
# The site people browse (bare origin). Issue links in comments point here.
|
|
150
|
+
WARDBY_JIRA_SITE_URL=
|
|
151
|
+
# The service account's API token and the Atlassian API gateway for your site.
|
|
152
|
+
WARDBY_JIRA_API_TOKEN=
|
|
153
|
+
WARDBY_JIRA_API_BASE_URL=https://api.atlassian.com/ex/jira/<cloudId>
|
|
154
|
+
# Optional: the token's expiry date (YYYY-MM-DD); wardby warns 14 days ahead.
|
|
155
|
+
WARDBY_JIRA_API_TOKEN_EXPIRES_AT=
|
|
156
|
+
# Optional: field id of the legacy Epic Link field (company-managed projects), so
|
|
157
|
+
# cost reports group runs under their epic, e.g. customfield_10014.
|
|
158
|
+
# WARDBY_JIRA_EPIC_LINK_FIELD=
|
|
159
|
+
# The secret you set on the Jira webhook (20+ characters).
|
|
160
|
+
WARDBY_JIRA_WEBHOOK_SECRET=
|
|
161
|
+
|
|
143
162
|
# EXECUTOR=in-process (default): heartbeat + reconciler durability; a run whose
|
|
144
163
|
# process dies is reconciled to `lost`. EXECUTOR=dbos: each run is a DBOS
|
|
145
164
|
# durable workflow that resumes from its last completed LLM turn / tool call
|
package/README.md
CHANGED
|
@@ -225,14 +225,14 @@ These are example systems a team can build and manage through MCP:
|
|
|
225
225
|
**Builders can vary. The controls do not.** Each system inherits the same
|
|
226
226
|
budget, capability, identity, evidence, and review contract.
|
|
227
227
|
|
|
228
|
-
| Agent system
|
|
229
|
-
|
|
|
230
|
-
| **Delivery pipeline**
|
|
231
|
-
| **Security maintenance**
|
|
232
|
-
| **Architecture review**
|
|
233
|
-
| **QA coverage**
|
|
234
|
-
| **System monitoring**
|
|
235
|
-
| **Project tracking**
|
|
228
|
+
| Agent system | Typical cycle | Governed outcome |
|
|
229
|
+
| -------------------------------------------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------ |
|
|
230
|
+
| **[Delivery pipeline](docs/agent-recipes.md#recipe-b-a-builder-per-language)** | Work request → plan → implementation → tests → review | Optional draft feature or bug-fix PR |
|
|
231
|
+
| **Security maintenance** | Scheduled scan → assess → patch → verify | Report or optional remediation PR |
|
|
232
|
+
| **[Architecture review](docs/agent-recipes.md#recipe-a-an-architecture-keeper)** | Inspect codebase → score risks → prioritize findings | Architecture and risk report |
|
|
233
|
+
| **QA coverage** | Map journeys → rank gaps → add tests → run suite | Coverage report or optional test PR |
|
|
234
|
+
| **System monitoring** | Receive signal → investigate → correlate → escalate | Actionable defect or incident report |
|
|
235
|
+
| **Project tracking** | Read delivery data → compare plan, cost, and progress → flag drift | Portfolio or program update |
|
|
236
236
|
|
|
237
237
|
Agents can stand alone or be connected as bounded sub-agents. Shared budget
|
|
238
238
|
groups can cap the combined spend of an ecosystem, while each agent retains its
|
|
@@ -364,7 +364,9 @@ for the repository's own dependency-override policy.
|
|
|
364
364
|
- MCP-first agent, tool, schedule, budget-group, secret, datastore, webhook,
|
|
365
365
|
run, and sub-agent management.
|
|
366
366
|
- Scheduled and event-triggered native agents with OpenAI, direct Anthropic,
|
|
367
|
-
and Bedrock-Claude model routing
|
|
367
|
+
and Bedrock-Claude model routing, priced and routed from an admin-editable
|
|
368
|
+
model catalog (`list_models`, `get_model`, `set_model`, `disable_model`,
|
|
369
|
+
`reset_model`).
|
|
368
370
|
- Per-run budgets, shared budget groups, usage accounting, and cancellation.
|
|
369
371
|
- QuickJS tool isolation with host allowlists and secret bindings.
|
|
370
372
|
- Containerized Codex and Claude Code executors with trusted GitHub draft-PR
|
package/dist/cli-help.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const CLI_USAGE = "usage:\n wardby quickstart [--provider openai|anthropic] [--model <m>] [--budget <usd>] [--client none|codex|claude|both] [--skip-demo] [--non-interactive --yes]\n wardby doctor\n wardby status\n wardby logs [--tail N] [--follow]\n wardby down [--volumes]\n wardby help [list]\n wardby help search <terms>\n wardby help open <article-id>\n wardby agent create --name <n> --model <m> --prompt <p> --budget <usd> [--schedule \"<cron>\"] [--timezone <tz>] [--max-turns <n>] [--owner <subject>] [--public]\n (owned by --owner or LOCAL_PRINCIPAL; --public shares it with everyone at execute)\n wardby agent list\n wardby agent schedule <name> --cron \"<expr>\" [--timezone <tz>] [--disable]\n wardby tool create --name <n> --description <d> --params <file> --code <file>\n wardby tool attach <tool-name|tool-id> <agent-name> (grants the capabilities in the agent owner's name)\n wardby tool detach <tool-name|tool-id> <agent-name>\n wardby tool update <tool-name|tool-id> [--description <d>] [--params <file>] [--code <file>]\n wardby tool delete <tool-name|tool-id> [--detach]\n wardby tool list [--agent <name>]\n wardby run <name>\n wardby runs [--agent <name>] [--limit N] [--status <s>]\n wardby coding preflight (JOB_LAUNCHER=docker or kubernetes)\n wardby coding cleanup --run-id <id>\n wardby scheduler [--scope default]\n wardby mcp (MCP_TRANSPORT=stdio|http selects the transport)\n wardby serve [--scope default] (mcp + scheduler + reconciler in one process; http only)\n wardby grants migration-report [--json]\n wardby grants adopt-public --owner <subject> [--dry-run] [--keep-everyone-execute]\n wardby grants prune-bindings [--dry-run]\n wardby import <bundle-dir> [--owner <subject>] [--public] [--include-secrets --transfer-key <pem>] [--default-budget <usd>] [--dry-run] [--prefix <p>] [--on-conflict fail|skip|rename] [--allow-open-fetch]\n\noptions:\n -h, --help Show this help\n -v, --version Show the installed Wardby version";
|
|
1
|
+
export declare const CLI_USAGE = "usage:\n wardby quickstart [--provider openai|anthropic] [--model <m>] [--budget <usd>] [--client none|codex|claude|both] [--skip-demo] [--non-interactive --yes]\n wardby doctor\n wardby status\n wardby logs [--tail N] [--follow]\n wardby down [--volumes]\n wardby knowledge check [dir] [--root <dir>] [--strict] [--json] Validate a knowledge bundle (offline)\n wardby help [list]\n wardby help search <terms>\n wardby help open <article-id>\n wardby agent create --name <n> --model <m> --prompt <p> --budget <usd> [--schedule \"<cron>\"] [--timezone <tz>] [--max-turns <n>] [--owner <subject>] [--public]\n (owned by --owner or LOCAL_PRINCIPAL; --public shares it with everyone at execute)\n wardby agent list\n wardby agent schedule <name> --cron \"<expr>\" [--timezone <tz>] [--disable]\n wardby tool create --name <n> --description <d> --params <file> --code <file>\n wardby tool attach <tool-name|tool-id> <agent-name> (grants the capabilities in the agent owner's name)\n wardby tool detach <tool-name|tool-id> <agent-name>\n wardby tool update <tool-name|tool-id> [--description <d>] [--params <file>] [--code <file>]\n wardby tool delete <tool-name|tool-id> [--detach]\n wardby tool list [--agent <name>]\n wardby run <name>\n wardby runs [--agent <name>] [--limit N] [--status <s>]\n wardby coding preflight (JOB_LAUNCHER=docker or kubernetes)\n wardby coding cleanup --run-id <id>\n wardby scheduler [--scope default]\n wardby mcp (MCP_TRANSPORT=stdio|http selects the transport)\n wardby serve [--scope default] (mcp + scheduler + reconciler in one process; http only)\n wardby grants migration-report [--json]\n wardby grants adopt-public --owner <subject> [--dry-run] [--keep-everyone-execute]\n wardby grants prune-bindings [--dry-run]\n wardby import <bundle-dir> [--owner <subject>] [--public] [--include-secrets --transfer-key <pem>] [--default-budget <usd>] [--dry-run] [--prefix <p>] [--on-conflict fail|skip|rename] [--allow-open-fetch]\n\noptions:\n -h, --help Show this help\n -v, --version Show the installed Wardby version";
|
package/dist/cli-help.js
CHANGED
|
@@ -4,6 +4,7 @@ export const CLI_USAGE = `usage:
|
|
|
4
4
|
wardby status
|
|
5
5
|
wardby logs [--tail N] [--follow]
|
|
6
6
|
wardby down [--volumes]
|
|
7
|
+
wardby knowledge check [dir] [--root <dir>] [--strict] [--json] Validate a knowledge bundle (offline)
|
|
7
8
|
wardby help [list]
|
|
8
9
|
wardby help search <terms>
|
|
9
10
|
wardby help open <article-id>
|
package/dist/cli.js
CHANGED
|
@@ -26,6 +26,7 @@ import { isImmutableDockerImage } from "./providers/jobs/docker-isolation.js";
|
|
|
26
26
|
import { ClientNodeKubernetesApi } from "./providers/jobs/kubernetes-client.js";
|
|
27
27
|
import { describePreflightFailure, kubernetesPreflight } from "./providers/jobs/kubernetes-preflight.js";
|
|
28
28
|
import { RoutingLlmProvider, isLlmEffort, modelSupportedEfforts, resolveLlmRegistrations, } from "./providers/llm/index.js";
|
|
29
|
+
import { startModelCatalog } from "./providers/llm/catalog-store.js";
|
|
29
30
|
import { buildConfiguredExecutor, buildExecutor } from "./providers/executor/index.js";
|
|
30
31
|
import { PostgresDatastore } from "./providers/datastore/index.js";
|
|
31
32
|
import { PostgresAgentMemory } from "./providers/memory/index.js";
|
|
@@ -33,9 +34,11 @@ import { buildSecretCipher } from "./providers/secrets/index.js";
|
|
|
33
34
|
import { prisma } from "./core/db.js";
|
|
34
35
|
import { runAgent } from "./core/runner.js";
|
|
35
36
|
import { cancelRunOnSignal } from "./core/run-heartbeat.js";
|
|
37
|
+
import { buildIssueTrackers } from "./providers/issue-tracker/index.js";
|
|
36
38
|
import { buildReviewHosts } from "./providers/review-host/index.js";
|
|
37
39
|
import { createRepoAccessGate } from "./core/repo-access.js";
|
|
38
40
|
import { validateCronExpression } from "./core/cron.js";
|
|
41
|
+
import { assertAgentModelAvailable } from "./core/run-pricing.js";
|
|
39
42
|
import { startScheduler } from "./core/scheduler.js";
|
|
40
43
|
import { startReconciler } from "./core/reconciler.js";
|
|
41
44
|
import { NativeEngine } from "./core/engine-native.js";
|
|
@@ -140,6 +143,22 @@ async function agentCreate(args) {
|
|
|
140
143
|
fail(`invalid --schedule/--timezone: ${err instanceof Error ? err.message : String(err)}`);
|
|
141
144
|
}
|
|
142
145
|
}
|
|
146
|
+
// Started before validating the model so an admin override recorded in
|
|
147
|
+
// ModelCatalogEntry (a disabled model, a narrowed effort list) is
|
|
148
|
+
// respected here too, not just at run time.
|
|
149
|
+
let modelCatalog;
|
|
150
|
+
try {
|
|
151
|
+
modelCatalog = await startModelCatalog(prisma);
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
assertAgentModelAvailable(values.model);
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
161
|
+
}
|
|
143
162
|
if (values.effort !== undefined) {
|
|
144
163
|
const accepted = modelSupportedEfforts(values.model);
|
|
145
164
|
if (!isLlmEffort(values.effort) || !accepted.includes(values.effort)) {
|
|
@@ -147,6 +166,7 @@ async function agentCreate(args) {
|
|
|
147
166
|
(accepted.length > 0 ? ` (accepted: ${accepted.join(", ")}).` : " (it accepts no effort setting)."));
|
|
148
167
|
}
|
|
149
168
|
}
|
|
169
|
+
modelCatalog.close();
|
|
150
170
|
// Owned by --owner or the local operator; never owner-less (resource-
|
|
151
171
|
// sharing grants spec §3.8). --public shares it with everyone at execute.
|
|
152
172
|
let ownerId;
|
|
@@ -510,6 +530,13 @@ async function run(name) {
|
|
|
510
530
|
if (agent.kind === "coding") {
|
|
511
531
|
fail("Coding agents are MCP-first: use trigger_agent so task input and ownership are recorded safely.");
|
|
512
532
|
}
|
|
533
|
+
let modelCatalog;
|
|
534
|
+
try {
|
|
535
|
+
modelCatalog = await startModelCatalog(prisma);
|
|
536
|
+
}
|
|
537
|
+
catch (err) {
|
|
538
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
539
|
+
}
|
|
513
540
|
const llm = buildLlmProvider();
|
|
514
541
|
const engine = buildEngine();
|
|
515
542
|
const secrets = buildSecrets();
|
|
@@ -520,7 +547,7 @@ async function run(name) {
|
|
|
520
547
|
// `running` row behind.
|
|
521
548
|
let removeSignalHandlers;
|
|
522
549
|
try {
|
|
523
|
-
run = await runAgent(name, { llm, engine, datastore, secrets, memory, reviewHosts: buildReviewHosts() }, prisma, (delta) => {
|
|
550
|
+
run = await runAgent(name, { llm, engine, datastore, secrets, memory, reviewHosts: buildReviewHosts(), issueTrackers: buildIssueTrackers() }, prisma, (delta) => {
|
|
524
551
|
process.stdout.write(delta);
|
|
525
552
|
}, (created) => {
|
|
526
553
|
removeSignalHandlers = cancelRunOnSignal(prisma, created.id);
|
|
@@ -531,6 +558,7 @@ async function run(name) {
|
|
|
531
558
|
}
|
|
532
559
|
finally {
|
|
533
560
|
removeSignalHandlers?.();
|
|
561
|
+
modelCatalog.close();
|
|
534
562
|
}
|
|
535
563
|
process.stdout.write("\n");
|
|
536
564
|
const completedAgent = await prisma.agent.findUnique({ where: { id: run.agentId } });
|
|
@@ -657,6 +685,13 @@ async function scheduler(args) {
|
|
|
657
685
|
// Parsed before anything starts, so a malformed CODING_MAX_CONCURRENT or
|
|
658
686
|
// CODING_QUEUE_TIMEOUT_SEC fails fast.
|
|
659
687
|
const concurrency = loadCodingConcurrencyConfig();
|
|
688
|
+
let modelCatalog;
|
|
689
|
+
try {
|
|
690
|
+
modelCatalog = await startModelCatalog(prisma);
|
|
691
|
+
}
|
|
692
|
+
catch (err) {
|
|
693
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
694
|
+
}
|
|
660
695
|
const config = loadProviderConfig();
|
|
661
696
|
const llm = buildLlmProvider();
|
|
662
697
|
const engine = buildEngine();
|
|
@@ -664,18 +699,21 @@ async function scheduler(args) {
|
|
|
664
699
|
const datastore = buildDatastore(secrets);
|
|
665
700
|
const memory = buildMemory();
|
|
666
701
|
const reviewHosts = buildReviewHosts();
|
|
702
|
+
const issueTrackers = buildIssueTrackers();
|
|
667
703
|
// One repository-access gate (and cache) for native repo_* calls and coding runs.
|
|
668
704
|
const repoAccess = createRepoAccessGate({ db: prisma, hosts: reviewHosts });
|
|
669
|
-
const nativeExecutor = buildExecutor(config, { llm, engine, datastore, secrets, memory, reviewHosts, repoAccess }, prisma);
|
|
705
|
+
const nativeExecutor = buildExecutor(config, { llm, engine, datastore, secrets, memory, reviewHosts, issueTrackers, repoAccess }, prisma);
|
|
670
706
|
const executor = buildConfiguredExecutor({ native: nativeExecutor, db: prisma, providerConfig: config, repoAccess });
|
|
671
707
|
await executor.launch?.();
|
|
672
|
-
const reconciler = startReconciler({ db: prisma, executor, reviewHosts });
|
|
708
|
+
const reconciler = startReconciler({ db: prisma, executor, reviewHosts, issueTrackers });
|
|
709
|
+
const selfDefects = { db: prisma, issueTrackers };
|
|
673
710
|
const sched = startScheduler({
|
|
674
711
|
executor,
|
|
675
712
|
db: prisma,
|
|
676
713
|
scope,
|
|
714
|
+
selfDefects,
|
|
677
715
|
onLeaderTick: async () => {
|
|
678
|
-
await drainCodingQueue({ db: prisma, executor, ...concurrency });
|
|
716
|
+
await drainCodingQueue({ db: prisma, executor, ...concurrency, selfDefects });
|
|
679
717
|
},
|
|
680
718
|
});
|
|
681
719
|
console.log(`wardby scheduler started (scope "${scope}"). Press Ctrl+C to stop.`);
|
|
@@ -686,7 +724,10 @@ async function scheduler(args) {
|
|
|
686
724
|
reconciler.stop();
|
|
687
725
|
void Promise.resolve(executor.close?.())
|
|
688
726
|
.catch((err) => cliLog.warn({ err }, "executor close failed during scheduler shutdown"))
|
|
689
|
-
.finally(
|
|
727
|
+
.finally(() => {
|
|
728
|
+
modelCatalog.close();
|
|
729
|
+
resolve();
|
|
730
|
+
});
|
|
690
731
|
};
|
|
691
732
|
process.once("SIGINT", shutdown);
|
|
692
733
|
process.once("SIGTERM", shutdown);
|
|
@@ -734,11 +775,28 @@ async function serve(args) {
|
|
|
734
775
|
}
|
|
735
776
|
async function importCommand(rest) {
|
|
736
777
|
const opts = parseImportArgs(rest);
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
778
|
+
// runImport builds an LLM provider and classifies each agent's model as
|
|
779
|
+
// routable/unroutable from currentModelCatalog() — without a store
|
|
780
|
+
// started here first, that falls back to the shipped catalog, so an
|
|
781
|
+
// admin-disabled model would be (wrongly) imported as routable and a
|
|
782
|
+
// DB-only custom model would be (wrongly) imported as disabled.
|
|
783
|
+
let modelCatalog;
|
|
784
|
+
try {
|
|
785
|
+
modelCatalog = await startModelCatalog(prisma);
|
|
786
|
+
}
|
|
787
|
+
catch (err) {
|
|
788
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
789
|
+
}
|
|
790
|
+
try {
|
|
791
|
+
const { report, result } = await runImport({ ...opts, db: prisma, env: process.env });
|
|
792
|
+
console.log(report);
|
|
793
|
+
if (result) {
|
|
794
|
+
for (const w of result.webhookSecrets)
|
|
795
|
+
console.log(`webhook secret (${w.agentName}): ${w.secret}`);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
finally {
|
|
799
|
+
modelCatalog.close();
|
|
742
800
|
}
|
|
743
801
|
}
|
|
744
802
|
async function main() {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The coding workspace has no git metadata, so the worker cannot learn which
|
|
3
|
+
* commit it is looking at. The executor appends it to the task when it writes
|
|
4
|
+
* the worker input (after cloning). Never grows a task past the limit.
|
|
5
|
+
*/
|
|
6
|
+
export declare function withBaseCommit(task: string, baseCommit: string): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { byteLength, MAX_CODING_TASK_BYTES } from "./protocol.js";
|
|
2
|
+
/**
|
|
3
|
+
* The coding workspace has no git metadata, so the worker cannot learn which
|
|
4
|
+
* commit it is looking at. The executor appends it to the task when it writes
|
|
5
|
+
* the worker input (after cloning). Never grows a task past the limit.
|
|
6
|
+
*/
|
|
7
|
+
export function withBaseCommit(task, baseCommit) {
|
|
8
|
+
if (!/^[0-9a-f]{40}$/.test(baseCommit))
|
|
9
|
+
return task;
|
|
10
|
+
const withLine = `${task}\n\nBase commit: ${baseCommit} (the commit this workspace was checked out at; the workspace has no git metadata).`;
|
|
11
|
+
return byteLength(withLine) <= MAX_CODING_TASK_BYTES ? withLine : task;
|
|
12
|
+
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
export declare const CODING_PROTOCOL_VERSION: 1;
|
|
3
|
+
/** The code host every coding run's repository and pull request live on (see pullRequestUrlSchema); the one place to change when another is added. */
|
|
4
|
+
export declare const CODING_CODE_PROVIDER = "github";
|
|
3
5
|
export declare const MAX_CODING_ARTIFACT_BYTES: number;
|
|
4
6
|
export declare const MAX_CODING_TASK_BYTES: number;
|
|
7
|
+
/** Room reserved at the end of a composed task for lines the executor appends (e.g. the base commit). */
|
|
8
|
+
export declare const CODING_TASK_TRAILER_RESERVE_BYTES = 256;
|
|
5
9
|
export declare const MAX_CODING_SUMMARY_BYTES: number;
|
|
6
10
|
export declare const MAX_CODING_TESTS = 64;
|
|
7
11
|
export declare const MAX_CODING_TEST_COMMAND_BYTES: number;
|
|
@@ -35,6 +39,15 @@ export declare function normalizeGitHubRepository(value: string): string;
|
|
|
35
39
|
export declare function normalizeGitRef(value: string): string;
|
|
36
40
|
export declare const CodingBaseRefSchema: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
|
|
37
41
|
export declare const CodingTaskOverrideSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>, string, string>;
|
|
42
|
+
/**
|
|
43
|
+
* A section rendered to fit whatever room the task leaves (the knowledge note:
|
|
44
|
+
* src/knowledge/note.ts). Declared structurally so this module, which coding
|
|
45
|
+
* workers ship as a single file, imports nothing new.
|
|
46
|
+
*/
|
|
47
|
+
export interface FittedSection {
|
|
48
|
+
/** The section text within `maxBytes`, or undefined when it does not fit. */
|
|
49
|
+
render(maxBytes: number): string | undefined;
|
|
50
|
+
}
|
|
38
51
|
/**
|
|
39
52
|
* A coding worker receives only its task text, so a coding agent's own
|
|
40
53
|
* instructions (its systemPrompt) travel inside that text, ahead of the
|
|
@@ -42,8 +55,11 @@ export declare const CodingTaskOverrideSchema: z.ZodEffects<z.ZodEffects<z.ZodEf
|
|
|
42
55
|
* started: src/coding/services/note.ts). Blank instructions and no note leave
|
|
43
56
|
* the task unchanged. The combination must still fit MAX_CODING_TASK_BYTES;
|
|
44
57
|
* exceeding it is an error naming both parts rather than a silent truncation.
|
|
58
|
+
* A knowledge note, when given, goes between the standing instructions and the
|
|
59
|
+
* request and is fitted into whatever room is left: it is cut, or dropped,
|
|
60
|
+
* never the cause of an error.
|
|
45
61
|
*/
|
|
46
|
-
export declare function composeCodingTask(instructions: string | null | undefined, task: string, note?: string): string;
|
|
62
|
+
export declare function composeCodingTask(instructions: string | null | undefined, task: string, note?: string, knowledge?: FittedSection): string;
|
|
47
63
|
/**
|
|
48
64
|
* The tag only labels a pull request title, so a model's malformed tag must not
|
|
49
65
|
* sink an otherwise finished run. A tag that already passes is kept as-is; any
|
package/dist/coding/protocol.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
export const CODING_PROTOCOL_VERSION = 1;
|
|
3
|
+
/** The code host every coding run's repository and pull request live on (see pullRequestUrlSchema); the one place to change when another is added. */
|
|
4
|
+
export const CODING_CODE_PROVIDER = "github";
|
|
3
5
|
export const MAX_CODING_ARTIFACT_BYTES = 64 * 1024;
|
|
4
6
|
export const MAX_CODING_TASK_BYTES = 16 * 1024;
|
|
7
|
+
/** Room reserved at the end of a composed task for lines the executor appends (e.g. the base commit). */
|
|
8
|
+
export const CODING_TASK_TRAILER_RESERVE_BYTES = 256;
|
|
5
9
|
export const MAX_CODING_SUMMARY_BYTES = 8 * 1024;
|
|
6
10
|
export const MAX_CODING_TESTS = 64;
|
|
7
11
|
export const MAX_CODING_TEST_COMMAND_BYTES = 2 * 1024;
|
|
@@ -201,17 +205,24 @@ export const CodingTaskOverrideSchema = boundedText(MAX_CODING_TASK_BYTES);
|
|
|
201
205
|
* started: src/coding/services/note.ts). Blank instructions and no note leave
|
|
202
206
|
* the task unchanged. The combination must still fit MAX_CODING_TASK_BYTES;
|
|
203
207
|
* exceeding it is an error naming both parts rather than a silent truncation.
|
|
208
|
+
* A knowledge note, when given, goes between the standing instructions and the
|
|
209
|
+
* request and is fitted into whatever room is left: it is cut, or dropped,
|
|
210
|
+
* never the cause of an error.
|
|
204
211
|
*/
|
|
205
|
-
export function composeCodingTask(instructions, task, note) {
|
|
212
|
+
export function composeCodingTask(instructions, task, note, knowledge) {
|
|
206
213
|
const standing = [instructions?.trim(), note?.trim()].filter((part) => Boolean(part)).join("\n\n");
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const composed = `Standing instructions for this coding agent:\n${standing}\n\nRequest:\n${task}`;
|
|
210
|
-
if (byteLength(composed) > MAX_CODING_TASK_BYTES) {
|
|
214
|
+
const base = standing ? `Standing instructions for this coding agent:\n${standing}\n\nRequest:\n${task}` : task;
|
|
215
|
+
if (byteLength(base) > MAX_CODING_TASK_BYTES) {
|
|
211
216
|
throw new Error(`The coding agent's instructions (${byteLength(standing)} bytes) plus this task (${byteLength(task)} bytes) ` +
|
|
212
217
|
`exceed the ${MAX_CODING_TASK_BYTES}-byte coding task limit; shorten one of them.`);
|
|
213
218
|
}
|
|
214
|
-
|
|
219
|
+
if (!knowledge)
|
|
220
|
+
return base;
|
|
221
|
+
const prefix = standing ? `Standing instructions for this coding agent:\n${standing}\n\n` : "";
|
|
222
|
+
const suffix = `\n\nRequest:\n${task}`;
|
|
223
|
+
const room = MAX_CODING_TASK_BYTES - byteLength(prefix) - byteLength(suffix) - CODING_TASK_TRAILER_RESERVE_BYTES;
|
|
224
|
+
const rendered = room > 0 ? knowledge.render(room) : undefined;
|
|
225
|
+
return rendered ? `${prefix}${rendered}${suffix}` : base;
|
|
215
226
|
}
|
|
216
227
|
const runIdSchema = boundedText(MAX_RUN_ID_BYTES, true).refine((value) => /^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(value), "must be an opaque identifier");
|
|
217
228
|
const modelSchema = boundedText(MAX_MODEL_BYTES, true).refine((value) => /^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(value), "must be a model identifier");
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
import type { ModelCatalog } from "../providers/llm/catalog.js";
|
|
2
|
+
import type { ModelProvider } from "../providers/llm/catalog-types.js";
|
|
1
3
|
export declare const CODING_PROVIDERS: readonly ["codex", "claude-code"];
|
|
2
4
|
export type CodingProvider = (typeof CODING_PROVIDERS)[number];
|
|
3
|
-
|
|
5
|
+
/** Which coding provider runs a model provider's models; Bedrock models are never coding models. */
|
|
6
|
+
export declare function codingProviderForModelProvider(provider: ModelProvider): CodingProvider | undefined;
|
|
7
|
+
export declare function codingProviderSupportsModel(provider: CodingProvider, model: string, catalog?: ModelCatalog): boolean;
|
|
8
|
+
export declare function assertCodingProvider(provider: string): asserts provider is CodingProvider;
|
|
4
9
|
export declare function assertCodingProviderModel(provider: string, model: string): asserts provider is CodingProvider;
|
package/dist/coding/provider.js
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { supportedModels as openaiSupportedModels } from "../providers/llm/pricing.js";
|
|
1
|
+
import { currentModelCatalog } from "../providers/llm/catalog-store.js";
|
|
3
2
|
export const CODING_PROVIDERS = ["codex", "claude-code"];
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
/** Which coding provider runs a model provider's models; Bedrock models are never coding models. */
|
|
4
|
+
export function codingProviderForModelProvider(provider) {
|
|
5
|
+
if (provider === "openai")
|
|
6
|
+
return "codex";
|
|
7
|
+
if (provider === "anthropic")
|
|
8
|
+
return "claude-code";
|
|
9
|
+
return undefined;
|
|
10
10
|
}
|
|
11
|
-
export function
|
|
11
|
+
export function codingProviderSupportsModel(provider, model, catalog = currentModelCatalog()) {
|
|
12
|
+
const entry = catalog.get(model);
|
|
13
|
+
return entry !== undefined && codingProviderForModelProvider(entry.provider) === provider;
|
|
14
|
+
}
|
|
15
|
+
export function assertCodingProvider(provider) {
|
|
12
16
|
if (!CODING_PROVIDERS.includes(provider)) {
|
|
13
17
|
throw new Error(`Unsupported coding provider "${provider}".`);
|
|
14
18
|
}
|
|
19
|
+
}
|
|
20
|
+
export function assertCodingProviderModel(provider, model) {
|
|
21
|
+
assertCodingProvider(provider);
|
|
15
22
|
if (!codingProviderSupportsModel(provider, model)) {
|
|
16
23
|
throw new Error(`Model "${model}" is not supported by coding provider "${provider}".`);
|
|
17
24
|
}
|
|
@@ -52,6 +52,31 @@ export declare function loadGitHubUserAuthConfig(env?: NodeJS.ProcessEnv): {
|
|
|
52
52
|
clientId?: string;
|
|
53
53
|
clientSecret?: string;
|
|
54
54
|
};
|
|
55
|
+
export interface JiraConfig {
|
|
56
|
+
/** The site people browse, e.g. https://your-site.atlassian.net — used for issue links. A bare https origin. */
|
|
57
|
+
siteUrl: string;
|
|
58
|
+
/** Where REST calls go: always https://api.atlassian.com/ex/jira/<cloudId>, the only base a service-account token works against. */
|
|
59
|
+
apiBaseUrl: string;
|
|
60
|
+
/** A service account's API token, sent as a Bearer token. Personal (Basic email:token) auth is not supported. */
|
|
61
|
+
auth: {
|
|
62
|
+
kind: "bearer";
|
|
63
|
+
token: string;
|
|
64
|
+
};
|
|
65
|
+
webhookSecret: string;
|
|
66
|
+
/** Optional, for expiry warnings; Atlassian API tokens last at most a year. */
|
|
67
|
+
tokenExpiresAt?: Date;
|
|
68
|
+
/** Company-managed sites still on the legacy Epic Link field: its id (customfield_N). Optional. */
|
|
69
|
+
epicLinkField?: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Jira Cloud issue-tracker settings (docs/jira-agents.md). Unset = Jira is
|
|
73
|
+
* disabled. wardby acts in Jira only as an Atlassian service account, so
|
|
74
|
+
* everything an agent does is attributed to that account: the token is sent
|
|
75
|
+
* as a Bearer token and REST calls go through the api.atlassian.com gateway
|
|
76
|
+
* (WARDBY_JIRA_API_BASE_URL, required). Personal tokens (Basic email:token)
|
|
77
|
+
* are rejected.
|
|
78
|
+
*/
|
|
79
|
+
export declare function loadJiraConfig(env?: NodeJS.ProcessEnv): JiraConfig | null;
|
|
55
80
|
export interface ContainerExecutorConfig {
|
|
56
81
|
workerImage?: string;
|
|
57
82
|
claudeWorkerImage?: string;
|
package/dist/config/providers.js
CHANGED
|
@@ -64,6 +64,77 @@ export function loadGitHubUserAuthConfig(env = process.env) {
|
|
|
64
64
|
}
|
|
65
65
|
return { clientId, clientSecret };
|
|
66
66
|
}
|
|
67
|
+
const JIRA_GATEWAY = /^https:\/\/api\.atlassian\.com\/ex\/jira\/[0-9a-f-]{36}$/i;
|
|
68
|
+
function httpsOrigin(value, name) {
|
|
69
|
+
let url;
|
|
70
|
+
try {
|
|
71
|
+
url = new URL(value);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
throw new Error(`${name} is not a valid URL.`);
|
|
75
|
+
}
|
|
76
|
+
if (url.protocol !== "https:")
|
|
77
|
+
throw new Error(`${name} must be an https URL.`);
|
|
78
|
+
return value.replace(/\/+$/, "");
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Jira Cloud issue-tracker settings (docs/jira-agents.md). Unset = Jira is
|
|
82
|
+
* disabled. wardby acts in Jira only as an Atlassian service account, so
|
|
83
|
+
* everything an agent does is attributed to that account: the token is sent
|
|
84
|
+
* as a Bearer token and REST calls go through the api.atlassian.com gateway
|
|
85
|
+
* (WARDBY_JIRA_API_BASE_URL, required). Personal tokens (Basic email:token)
|
|
86
|
+
* are rejected.
|
|
87
|
+
*/
|
|
88
|
+
export function loadJiraConfig(env = process.env) {
|
|
89
|
+
const site = env.WARDBY_JIRA_SITE_URL?.trim();
|
|
90
|
+
const token = env.WARDBY_JIRA_API_TOKEN?.trim();
|
|
91
|
+
const secret = env.WARDBY_JIRA_WEBHOOK_SECRET?.trim();
|
|
92
|
+
const base = env.WARDBY_JIRA_API_BASE_URL?.trim();
|
|
93
|
+
const expires = env.WARDBY_JIRA_API_TOKEN_EXPIRES_AT?.trim() || undefined;
|
|
94
|
+
if (env.WARDBY_JIRA_API_EMAIL?.trim()) {
|
|
95
|
+
throw new Error("WARDBY_JIRA_API_EMAIL is no longer supported: personal (Basic email:token) Jira tokens are not accepted. " +
|
|
96
|
+
"Use an Atlassian service account's API token with WARDBY_JIRA_API_BASE_URL=https://api.atlassian.com/ex/jira/<cloudId>.");
|
|
97
|
+
}
|
|
98
|
+
if (!site && !token && !secret && !base)
|
|
99
|
+
return null;
|
|
100
|
+
if (!site || !token || !secret || !base) {
|
|
101
|
+
throw new Error("Set WARDBY_JIRA_SITE_URL, WARDBY_JIRA_API_BASE_URL, WARDBY_JIRA_API_TOKEN and WARDBY_JIRA_WEBHOOK_SECRET together, or none of them.");
|
|
102
|
+
}
|
|
103
|
+
if (secret.length < 20)
|
|
104
|
+
throw new Error("WARDBY_JIRA_WEBHOOK_SECRET must be at least 20 characters.");
|
|
105
|
+
const siteUrl = httpsOrigin(site, "WARDBY_JIRA_SITE_URL");
|
|
106
|
+
const siteParsed = new URL(siteUrl);
|
|
107
|
+
if (siteParsed.pathname !== "/" ||
|
|
108
|
+
siteParsed.search ||
|
|
109
|
+
siteParsed.hash ||
|
|
110
|
+
siteParsed.username ||
|
|
111
|
+
siteParsed.password) {
|
|
112
|
+
throw new Error("WARDBY_JIRA_SITE_URL must be a bare https origin such as https://your-site.atlassian.net.");
|
|
113
|
+
}
|
|
114
|
+
const apiBaseUrl = httpsOrigin(base, "WARDBY_JIRA_API_BASE_URL");
|
|
115
|
+
if (!JIRA_GATEWAY.test(apiBaseUrl)) {
|
|
116
|
+
throw new Error("WARDBY_JIRA_API_BASE_URL must be https://api.atlassian.com/ex/jira/<cloudId>, the only base a service " +
|
|
117
|
+
"account's API token works against.");
|
|
118
|
+
}
|
|
119
|
+
let tokenExpiresAt;
|
|
120
|
+
if (expires) {
|
|
121
|
+
tokenExpiresAt = new Date(expires);
|
|
122
|
+
if (Number.isNaN(tokenExpiresAt.getTime()))
|
|
123
|
+
throw new Error("WARDBY_JIRA_API_TOKEN_EXPIRES_AT must be an ISO date.");
|
|
124
|
+
}
|
|
125
|
+
const epicLinkField = env.WARDBY_JIRA_EPIC_LINK_FIELD?.trim() || undefined;
|
|
126
|
+
if (epicLinkField && !/^customfield_\d+$/.test(epicLinkField)) {
|
|
127
|
+
throw new Error("WARDBY_JIRA_EPIC_LINK_FIELD must be a custom field id such as customfield_10014.");
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
siteUrl,
|
|
131
|
+
apiBaseUrl,
|
|
132
|
+
auth: { kind: "bearer", token },
|
|
133
|
+
webhookSecret: secret,
|
|
134
|
+
...(tokenExpiresAt ? { tokenExpiresAt } : {}),
|
|
135
|
+
...(epicLinkField ? { epicLinkField } : {}),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
67
138
|
function optionalPositiveNumber(value, name, fallback) {
|
|
68
139
|
if (value === undefined)
|
|
69
140
|
return fallback;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cost attribution (docs/private/2026-10-01-issue-cost-attribution-design.md):
|
|
3
|
+
* every run in an issue-related run tree points at one WorkItem, with the
|
|
4
|
+
* item's parent frozen as of dispatch. Attribution comes from control-plane
|
|
5
|
+
* data only (the event, a stored PR link, a validated explicit key, or the
|
|
6
|
+
* parent run), never from model output. A snapshot is a network call, so it
|
|
7
|
+
* is taken before dispatch's persist transaction and never fails a dispatch.
|
|
8
|
+
*/
|
|
9
|
+
import type { Prisma, PrismaClient } from "#prisma";
|
|
10
|
+
import { type IssueSnapshot, type IssueTrackerProvider, type IssueTrackerRegistry } from "../providers/issue-tracker/types.js";
|
|
11
|
+
export type AttributionSource = "issue_event" | "linked_pr" | "explicit";
|
|
12
|
+
/** Resolved before the persist transaction; never contains a network call. */
|
|
13
|
+
export interface ResolvedWorkItem {
|
|
14
|
+
provider: string;
|
|
15
|
+
key: string;
|
|
16
|
+
scopeKey: string;
|
|
17
|
+
/** null = key-only (snapshot failed or no tracker); then existing WorkItem fields are kept. */
|
|
18
|
+
snapshot: IssueSnapshot | null;
|
|
19
|
+
}
|
|
20
|
+
export interface AttributionIntent {
|
|
21
|
+
source: AttributionSource;
|
|
22
|
+
item: ResolvedWorkItem;
|
|
23
|
+
}
|
|
24
|
+
/** A WorkItem snapshotted this recently is reused as-is, without calling the tracker. */
|
|
25
|
+
export declare const SNAPSHOT_CACHE_MS: number;
|
|
26
|
+
export declare const SNAPSHOT_TIMEOUT_MS = 3000;
|
|
27
|
+
/**
|
|
28
|
+
* Snapshot budget for a caller that is answering a request (a webhook, an
|
|
29
|
+
* MCP tool call, a host event): a short timeout and no 429 retry, so a slow
|
|
30
|
+
* or rate-limited tracker degrades to key-only attribution instead of
|
|
31
|
+
* stalling the response.
|
|
32
|
+
*/
|
|
33
|
+
export declare const RESPONSE_PATH_SNAPSHOT_BUDGET: {
|
|
34
|
+
readonly timeoutMs: 2000;
|
|
35
|
+
readonly retryOn429: false;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The work item a new attribution points at, snapshotted from the tracker
|
|
39
|
+
* unless a fresh snapshot is already stored. Never throws: a failed lookup or
|
|
40
|
+
* snapshot attributes by key only.
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveWorkItem(db: Pick<PrismaClient, "workItem">, trackers: IssueTrackerRegistry | undefined, provider: string, key: string, opts?: {
|
|
43
|
+
timeoutMs?: number;
|
|
44
|
+
retryOn429?: boolean;
|
|
45
|
+
now?: Date;
|
|
46
|
+
}): Promise<ResolvedWorkItem>;
|
|
47
|
+
export type AttributionTx = Pick<Prisma.TransactionClient, "workItem" | "runAttribution" | "codingRun" | "runIssueStatus">;
|
|
48
|
+
/**
|
|
49
|
+
* Writes the run's attribution inside dispatch's persist transaction.
|
|
50
|
+
* Precedence: the parent run's attribution, then the continued coding run's,
|
|
51
|
+
* then a pre-attribution issue (legacyIssue), then the caller's intent. Returns the item's provider/key (for
|
|
52
|
+
* CodingRun.issueProvider/issueKey), or null when the run is unattributed.
|
|
53
|
+
*/
|
|
54
|
+
export declare function attributeRun(tx: AttributionTx, runId: string, from: {
|
|
55
|
+
parentRunId?: string;
|
|
56
|
+
continuesCodingRunId?: string;
|
|
57
|
+
intent?: AttributionIntent;
|
|
58
|
+
}, now?: Date): Promise<{
|
|
59
|
+
provider: string;
|
|
60
|
+
key: string;
|
|
61
|
+
} | null>;
|
|
62
|
+
/** The issue a pull request was opened for (phase-3 IssuePullRequest); the earliest link when several exist. */
|
|
63
|
+
export declare function linkedPullRequestIssue(db: Pick<PrismaClient, "issuePullRequest">, pr: {
|
|
64
|
+
codeProvider: string;
|
|
65
|
+
repository: string;
|
|
66
|
+
number: number;
|
|
67
|
+
}): Promise<{
|
|
68
|
+
provider: string;
|
|
69
|
+
key: string;
|
|
70
|
+
} | null>;
|
|
71
|
+
/** Attribution for a run on a pull request that was opened for an issue; undefined when it has none. */
|
|
72
|
+
export declare function linkedPullRequestAttribution(db: Pick<PrismaClient, "issuePullRequest" | "workItem">, trackers: IssueTrackerRegistry | undefined, pr: {
|
|
73
|
+
codeProvider: string;
|
|
74
|
+
repository: string;
|
|
75
|
+
number: number;
|
|
76
|
+
}, opts?: {
|
|
77
|
+
timeoutMs?: number;
|
|
78
|
+
retryOn429?: boolean;
|
|
79
|
+
}): Promise<AttributionIntent | undefined>;
|
|
80
|
+
/** A refusal whose message is safe to return to the caller. */
|
|
81
|
+
export declare class AttributionError extends Error {
|
|
82
|
+
constructor(message: string);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* An issue a caller names on trigger_agent or a webhook. It decides whose
|
|
86
|
+
* cost this run counts toward, so it must be well formed and in a project the
|
|
87
|
+
* agent is linked to (AgentIssueProject) — otherwise the dispatch is refused.
|
|
88
|
+
* Issue keys are uppercase, so a key that differs only by case or surrounding
|
|
89
|
+
* spaces ("pay-241") is corrected rather than refused. `field` is the caller's
|
|
90
|
+
* name for the value (trigger_agent: issue; webhooks: wardbyIssue), so
|
|
91
|
+
* refusals name the field the caller actually sent.
|
|
92
|
+
*/
|
|
93
|
+
export declare function validateExplicitIssue(db: Pick<PrismaClient, "agentIssueProject">, agentId: string, issue: unknown, field?: string): Promise<{
|
|
94
|
+
provider: IssueTrackerProvider;
|
|
95
|
+
key: string;
|
|
96
|
+
}>;
|
|
97
|
+
export declare function explicitAttribution(db: Pick<PrismaClient, "agentIssueProject" | "workItem">, trackers: IssueTrackerRegistry | undefined, agentId: string, issue: unknown, opts?: {
|
|
98
|
+
timeoutMs?: number;
|
|
99
|
+
retryOn429?: boolean;
|
|
100
|
+
field?: string;
|
|
101
|
+
}): Promise<AttributionIntent>;
|