terramend 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +145 -0
- package/dist/agents/claude.d.ts +73 -0
- package/dist/agents/claudePretoolGate.d.ts +99 -0
- package/dist/agents/gateServer.d.ts +7 -0
- package/dist/agents/index.d.ts +6 -0
- package/dist/agents/nativeFsDenies.d.ts +28 -0
- package/dist/agents/opencode.d.ts +231 -0
- package/dist/agents/opencodePlugin.d.ts +85 -0
- package/dist/agents/opencodeShared.d.ts +40 -0
- package/dist/agents/postRun.d.ts +132 -0
- package/dist/agents/reviewer.d.ts +38 -0
- package/dist/agents/sessionLabeler.d.ts +97 -0
- package/dist/agents/shared.d.ts +189 -0
- package/dist/agents/subagentModels.d.ts +19 -0
- package/dist/agents/subagentToolGates.d.ts +55 -0
- package/dist/cli.mjs +197426 -0
- package/dist/external.d.ts +227 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +196783 -0
- package/dist/internal/index.d.ts +18 -0
- package/dist/internal.js +1714 -0
- package/dist/lifecycle.d.ts +2 -0
- package/dist/main.d.ts +8 -0
- package/dist/mcp/arkConfig.d.ts +1 -0
- package/dist/mcp/checkSuite.d.ts +25 -0
- package/dist/mcp/checkout.d.ts +77 -0
- package/dist/mcp/comment.d.ts +119 -0
- package/dist/mcp/commitInfo.d.ts +9 -0
- package/dist/mcp/crosswalk.d.ts +105 -0
- package/dist/mcp/dependencies.d.ts +8 -0
- package/dist/mcp/geminiSanitizer.d.ts +28 -0
- package/dist/mcp/git.d.ts +46 -0
- package/dist/mcp/guardrails.d.ts +104 -0
- package/dist/mcp/issue.d.ts +18 -0
- package/dist/mcp/issueComments.d.ts +9 -0
- package/dist/mcp/issueEvents.d.ts +9 -0
- package/dist/mcp/issueInfo.d.ts +9 -0
- package/dist/mcp/labels.d.ts +12 -0
- package/dist/mcp/localContext.d.ts +19 -0
- package/dist/mcp/moduleExtraction.d.ts +71 -0
- package/dist/mcp/moduleTests.d.ts +104 -0
- package/dist/mcp/modules.d.ts +179 -0
- package/dist/mcp/output.d.ts +12 -0
- package/dist/mcp/pathSafety.d.ts +14 -0
- package/dist/mcp/policy.d.ts +48 -0
- package/dist/mcp/pr.d.ts +49 -0
- package/dist/mcp/prInfo.d.ts +9 -0
- package/dist/mcp/providerSchema.d.ts +50 -0
- package/dist/mcp/review.d.ts +199 -0
- package/dist/mcp/reviewComments.d.ts +178 -0
- package/dist/mcp/roots.d.ts +58 -0
- package/dist/mcp/scope.d.ts +15 -0
- package/dist/mcp/selectMode.d.ts +18 -0
- package/dist/mcp/server.d.ts +48 -0
- package/dist/mcp/shared.d.ts +47 -0
- package/dist/mcp/shell.d.ts +37 -0
- package/dist/mcp/staleFix.d.ts +51 -0
- package/dist/mcp/terraform/cost.d.ts +55 -0
- package/dist/mcp/terraform/currency.d.ts +94 -0
- package/dist/mcp/terraform/decisions.d.ts +178 -0
- package/dist/mcp/terraform/findings.d.ts +75 -0
- package/dist/mcp/terraform/plan.d.ts +157 -0
- package/dist/mcp/terraform/scanners.d.ts +131 -0
- package/dist/mcp/terraform/tools.d.ts +63 -0
- package/dist/mcp/terraform/types.d.ts +172 -0
- package/dist/mcp/terraform.d.ts +22 -0
- package/dist/mcp/terratest.d.ts +83 -0
- package/dist/mcp/upload.d.ts +6 -0
- package/dist/models.d.ts +171 -0
- package/dist/modes.d.ts +26 -0
- package/dist/prep/index.d.ts +7 -0
- package/dist/prep/installNodeDependencies.d.ts +2 -0
- package/dist/prep/installPythonDependencies.d.ts +2 -0
- package/dist/prep/types.d.ts +31 -0
- package/dist/reviewQuality.d.ts +64 -0
- package/dist/skills/terraform-best-practices/SKILL.md +369 -0
- package/dist/toolState.d.ts +135 -0
- package/dist/utils/activity.d.ts +40 -0
- package/dist/utils/agent.d.ts +20 -0
- package/dist/utils/agentHangReport.d.ts +38 -0
- package/dist/utils/apiFetch.d.ts +19 -0
- package/dist/utils/apiKeys.d.ts +41 -0
- package/dist/utils/apiUrl.d.ts +20 -0
- package/dist/utils/assets.d.ts +8 -0
- package/dist/utils/billingErrors.d.ts +85 -0
- package/dist/utils/body.d.ts +34 -0
- package/dist/utils/buildTerramendFooter.d.ts +25 -0
- package/dist/utils/byokFallback.d.ts +85 -0
- package/dist/utils/claudeSubscription.d.ts +30 -0
- package/dist/utils/cli.d.ts +10 -0
- package/dist/utils/codexHome.d.ts +29 -0
- package/dist/utils/codexOAuth.d.ts +60 -0
- package/dist/utils/diffCoverage.d.ts +63 -0
- package/dist/utils/errorReport.d.ts +17 -0
- package/dist/utils/exitHandler.d.ts +8 -0
- package/dist/utils/fixDoubleEscapedString.d.ts +1 -0
- package/dist/utils/gitAuth.d.ts +84 -0
- package/dist/utils/gitAuthServer.d.ts +24 -0
- package/dist/utils/github.d.ts +78 -0
- package/dist/utils/globals.d.ts +3 -0
- package/dist/utils/install.d.ts +60 -0
- package/dist/utils/instructions.d.ts +48 -0
- package/dist/utils/leapingComment.d.ts +11 -0
- package/dist/utils/learnings.d.ts +62 -0
- package/dist/utils/learningsTruncate.d.ts +25 -0
- package/dist/utils/lifecycle.d.ts +57 -0
- package/dist/utils/log.d.ts +111 -0
- package/dist/utils/normalizeEnv.d.ts +30 -0
- package/dist/utils/openCodeModels.d.ts +11 -0
- package/dist/utils/overrides.d.ts +40 -0
- package/dist/utils/packageManager.d.ts +49 -0
- package/dist/utils/patchWorkflowRunFields.d.ts +29 -0
- package/dist/utils/payload.d.ts +105 -0
- package/dist/utils/prSummary.d.ts +61 -0
- package/dist/utils/progressComment.d.ts +146 -0
- package/dist/utils/providerErrors.d.ts +31 -0
- package/dist/utils/rangeDiff.d.ts +51 -0
- package/dist/utils/remediationCommand.d.ts +55 -0
- package/dist/utils/retry.d.ts +13 -0
- package/dist/utils/reviewCleanup.d.ts +14 -0
- package/dist/utils/run.d.ts +9 -0
- package/dist/utils/runContext.d.ts +60 -0
- package/dist/utils/runContextData.d.ts +23 -0
- package/dist/utils/runErrorRenderer.d.ts +64 -0
- package/dist/utils/runLifecycle.d.ts +86 -0
- package/dist/utils/runStartupLog.d.ts +15 -0
- package/dist/utils/secrets.d.ts +22 -0
- package/dist/utils/setup.d.ts +90 -0
- package/dist/utils/shell.d.ts +32 -0
- package/dist/utils/skills.d.ts +10 -0
- package/dist/utils/subprocess.d.ts +80 -0
- package/dist/utils/terraformMcp.d.ts +42 -0
- package/dist/utils/time.d.ts +15 -0
- package/dist/utils/timer.d.ts +23 -0
- package/dist/utils/todoTracking.d.ts +16 -0
- package/dist/utils/token.d.ts +39 -0
- package/dist/utils/version.d.ts +2 -0
- package/dist/utils/versioning.d.ts +7 -0
- package/dist/utils/vertex.d.ts +16 -0
- package/dist/utils/workflow.d.ts +13 -0
- package/package.json +119 -0
- package/src/agents/claude.test.ts +1016 -0
- package/src/agents/claude.ts +1246 -0
- package/src/agents/claudePretoolGate.test.ts +28 -0
- package/src/agents/claudePretoolGate.ts +173 -0
- package/src/agents/gateServer.test.ts +204 -0
- package/src/agents/gateServer.ts +124 -0
- package/src/agents/index.ts +10 -0
- package/src/agents/nativeFsDenies.ts +82 -0
- package/src/agents/opencode.test.ts +1440 -0
- package/src/agents/opencode.ts +1312 -0
- package/src/agents/opencodePlugin.ts +222 -0
- package/src/agents/opencodeShared.test.ts +34 -0
- package/src/agents/opencodeShared.ts +121 -0
- package/src/agents/postRun.test.ts +549 -0
- package/src/agents/postRun.ts +535 -0
- package/src/agents/reviewer.ts +104 -0
- package/src/agents/sessionLabeler.test.ts +247 -0
- package/src/agents/sessionLabeler.ts +178 -0
- package/src/agents/shared.test.ts +76 -0
- package/src/agents/shared.ts +292 -0
- package/src/agents/subagentModels.test.ts +113 -0
- package/src/agents/subagentModels.ts +40 -0
- package/src/agents/subagentRegistration.test.ts +41 -0
- package/src/agents/subagentToolGates.ts +114 -0
- package/src/cli.test.ts +129 -0
- package/src/cli.ts +105 -0
- package/src/commands/gha.test.ts +192 -0
- package/src/commands/gha.ts +188 -0
- package/src/commands/mcp.ts +122 -0
- package/src/config.ts +1 -0
- package/src/entry.ts +7 -0
- package/src/entryPost.stdlibOnly.test.ts +109 -0
- package/src/entryPost.ts +99 -0
- package/src/external.test.ts +16 -0
- package/src/external.ts +302 -0
- package/src/index.ts +11 -0
- package/src/internal/index.ts +71 -0
- package/src/lifecycle.ts +2 -0
- package/src/main.test.ts +873 -0
- package/src/main.ts +712 -0
- package/src/mcp/__fixtures__/terramend-scratch-pr-49-review-3485940013.json +110 -0
- package/src/mcp/__fixtures__/terramend-scratch-pr-64-review-3531000326.json +14 -0
- package/src/mcp/__fixtures__/terramend-test-repo-pr-1.diff.json +67 -0
- package/src/mcp/__snapshots__/checkout.test.ts.snap +109 -0
- package/src/mcp/__snapshots__/reviewComments.test.ts.snap +71 -0
- package/src/mcp/arkConfig.ts +7 -0
- package/src/mcp/checkSuite.test.ts +245 -0
- package/src/mcp/checkSuite.ts +255 -0
- package/src/mcp/checkout.test.ts +752 -0
- package/src/mcp/checkout.ts +886 -0
- package/src/mcp/comment.test.ts +772 -0
- package/src/mcp/comment.ts +582 -0
- package/src/mcp/commitInfo.test.ts +127 -0
- package/src/mcp/commitInfo.ts +61 -0
- package/src/mcp/crosswalk.test.ts +106 -0
- package/src/mcp/crosswalk.ts +339 -0
- package/src/mcp/dependencies.test.ts +309 -0
- package/src/mcp/dependencies.ts +189 -0
- package/src/mcp/geminiSanitizer.test.ts +287 -0
- package/src/mcp/geminiSanitizer.ts +207 -0
- package/src/mcp/git.test.ts +1083 -0
- package/src/mcp/git.ts +890 -0
- package/src/mcp/guardrails.test.ts +705 -0
- package/src/mcp/guardrails.ts +465 -0
- package/src/mcp/issue.test.ts +113 -0
- package/src/mcp/issue.ts +73 -0
- package/src/mcp/issueComments.test.ts +69 -0
- package/src/mcp/issueComments.ts +48 -0
- package/src/mcp/issueEvents.test.ts +134 -0
- package/src/mcp/issueEvents.ts +100 -0
- package/src/mcp/issueInfo.test.ts +104 -0
- package/src/mcp/issueInfo.ts +72 -0
- package/src/mcp/labels.test.ts +52 -0
- package/src/mcp/labels.ts +34 -0
- package/src/mcp/localContext.ts +28 -0
- package/src/mcp/localServer.test.ts +75 -0
- package/src/mcp/localServer.ts +131 -0
- package/src/mcp/moduleExtraction.test.ts +261 -0
- package/src/mcp/moduleExtraction.ts +313 -0
- package/src/mcp/moduleTests.test.ts +269 -0
- package/src/mcp/moduleTests.ts +421 -0
- package/src/mcp/modules.test.ts +640 -0
- package/src/mcp/modules.ts +696 -0
- package/src/mcp/output.test.ts +96 -0
- package/src/mcp/output.ts +70 -0
- package/src/mcp/pathSafety.test.ts +44 -0
- package/src/mcp/pathSafety.ts +28 -0
- package/src/mcp/policy.test.ts +282 -0
- package/src/mcp/policy.ts +199 -0
- package/src/mcp/pr.test.ts +387 -0
- package/src/mcp/pr.ts +194 -0
- package/src/mcp/prInfo.test.ts +96 -0
- package/src/mcp/prInfo.ts +91 -0
- package/src/mcp/providerSchema.test.ts +85 -0
- package/src/mcp/providerSchema.ts +175 -0
- package/src/mcp/review.test.ts +936 -0
- package/src/mcp/review.ts +923 -0
- package/src/mcp/reviewComments.test.ts +549 -0
- package/src/mcp/reviewComments.ts +896 -0
- package/src/mcp/roots.test.ts +175 -0
- package/src/mcp/roots.ts +217 -0
- package/src/mcp/scope.test.ts +59 -0
- package/src/mcp/scope.ts +65 -0
- package/src/mcp/security.test.ts +720 -0
- package/src/mcp/selectMode.test.ts +210 -0
- package/src/mcp/selectMode.ts +181 -0
- package/src/mcp/server.test.ts +292 -0
- package/src/mcp/server.ts +403 -0
- package/src/mcp/shared.ts +100 -0
- package/src/mcp/shell.test.ts +520 -0
- package/src/mcp/shell.ts +505 -0
- package/src/mcp/staleFix.test.ts +237 -0
- package/src/mcp/staleFix.ts +277 -0
- package/src/mcp/terraform/cost.ts +163 -0
- package/src/mcp/terraform/currency.test.ts +338 -0
- package/src/mcp/terraform/currency.ts +336 -0
- package/src/mcp/terraform/decisions.ts +527 -0
- package/src/mcp/terraform/findings.ts +333 -0
- package/src/mcp/terraform/plan.ts +348 -0
- package/src/mcp/terraform/scanners.ts +809 -0
- package/src/mcp/terraform/tools.test.ts +1071 -0
- package/src/mcp/terraform/tools.ts +908 -0
- package/src/mcp/terraform/types.ts +305 -0
- package/src/mcp/terraform.test.ts +1957 -0
- package/src/mcp/terraform.ts +23 -0
- package/src/mcp/terratest.test.ts +105 -0
- package/src/mcp/terratest.ts +196 -0
- package/src/mcp/toolFiltering.test.ts +85 -0
- package/src/mcp/upload.test.ts +180 -0
- package/src/mcp/upload.ts +112 -0
- package/src/models.test.ts +300 -0
- package/src/models.ts +708 -0
- package/src/modes.test.ts +107 -0
- package/src/modes.ts +880 -0
- package/src/prep/index.ts +43 -0
- package/src/prep/installNodeDependencies.test.ts +298 -0
- package/src/prep/installNodeDependencies.ts +196 -0
- package/src/prep/installPythonDependencies.test.ts +268 -0
- package/src/prep/installPythonDependencies.ts +199 -0
- package/src/prep/types.ts +38 -0
- package/src/reviewQuality.test.ts +63 -0
- package/src/reviewQuality.ts +134 -0
- package/src/runCli.test.ts +214 -0
- package/src/runCli.ts +282 -0
- package/src/skills/terraform-best-practices/SKILL.md +369 -0
- package/src/toolState.test.ts +45 -0
- package/src/toolState.ts +252 -0
- package/src/utils/activity.test.ts +188 -0
- package/src/utils/activity.ts +210 -0
- package/src/utils/agent.test.ts +251 -0
- package/src/utils/agent.ts +139 -0
- package/src/utils/agentHangReport.test.ts +203 -0
- package/src/utils/agentHangReport.ts +170 -0
- package/src/utils/apiFetch.test.ts +115 -0
- package/src/utils/apiFetch.ts +62 -0
- package/src/utils/apiKeys.test.ts +344 -0
- package/src/utils/apiKeys.ts +206 -0
- package/src/utils/apiUrl.test.ts +30 -0
- package/src/utils/apiUrl.ts +59 -0
- package/src/utils/assets.test.ts +153 -0
- package/src/utils/assets.ts +107 -0
- package/src/utils/billingErrors.test.ts +121 -0
- package/src/utils/billingErrors.ts +189 -0
- package/src/utils/body.test.ts +217 -0
- package/src/utils/body.ts +168 -0
- package/src/utils/buildTerramendFooter.test.ts +38 -0
- package/src/utils/buildTerramendFooter.ts +82 -0
- package/src/utils/byokFallback.test.ts +205 -0
- package/src/utils/byokFallback.ts +128 -0
- package/src/utils/claudeSubscription.test.ts +179 -0
- package/src/utils/claudeSubscription.ts +93 -0
- package/src/utils/cli.ts +31 -0
- package/src/utils/codexHome.test.ts +190 -0
- package/src/utils/codexHome.ts +191 -0
- package/src/utils/codexOAuth.ts +147 -0
- package/src/utils/codexRefreshDetect.test.ts +85 -0
- package/src/utils/codexRefreshDetect.ts +35 -0
- package/src/utils/diffCoverage.test.ts +468 -0
- package/src/utils/diffCoverage.ts +404 -0
- package/src/utils/errorReport.test.ts +135 -0
- package/src/utils/errorReport.ts +83 -0
- package/src/utils/exitHandler.ts +35 -0
- package/src/utils/fixDoubleEscapedString.ts +9 -0
- package/src/utils/ghaCore.ts +13 -0
- package/src/utils/gitAuth.test.ts +322 -0
- package/src/utils/gitAuth.ts +263 -0
- package/src/utils/gitAuthServer.test.ts +260 -0
- package/src/utils/gitAuthServer.ts +182 -0
- package/src/utils/github.test.ts +615 -0
- package/src/utils/github.ts +538 -0
- package/src/utils/globals.ts +9 -0
- package/src/utils/humanEditCapture.test.ts +100 -0
- package/src/utils/humanEditCapture.ts +193 -0
- package/src/utils/install.test.ts +768 -0
- package/src/utils/install.ts +492 -0
- package/src/utils/instructions.test.ts +240 -0
- package/src/utils/instructions.ts +543 -0
- package/src/utils/leapingComment.test.ts +51 -0
- package/src/utils/leapingComment.ts +18 -0
- package/src/utils/learnings.test.ts +87 -0
- package/src/utils/learnings.ts +138 -0
- package/src/utils/learningsTocRender.test.ts +116 -0
- package/src/utils/learningsTruncate.test.ts +39 -0
- package/src/utils/learningsTruncate.ts +42 -0
- package/src/utils/lifecycle.test.ts +195 -0
- package/src/utils/lifecycle.ts +198 -0
- package/src/utils/log.test.ts +402 -0
- package/src/utils/log.ts +432 -0
- package/src/utils/normalizeEnv.test.ts +91 -0
- package/src/utils/normalizeEnv.ts +106 -0
- package/src/utils/openCodeModels.ts +82 -0
- package/src/utils/overrides.test.ts +89 -0
- package/src/utils/overrides.ts +98 -0
- package/src/utils/packageManager.test.ts +321 -0
- package/src/utils/packageManager.ts +257 -0
- package/src/utils/patchWorkflowRunFields.test.ts +92 -0
- package/src/utils/patchWorkflowRunFields.ts +150 -0
- package/src/utils/payload.test.ts +497 -0
- package/src/utils/payload.ts +371 -0
- package/src/utils/postApiFetch.ts +51 -0
- package/src/utils/prSummary.test.ts +224 -0
- package/src/utils/prSummary.ts +147 -0
- package/src/utils/progressComment.ts +261 -0
- package/src/utils/providerErrors.test.ts +315 -0
- package/src/utils/providerErrors.ts +172 -0
- package/src/utils/rangeDiff.test.ts +236 -0
- package/src/utils/rangeDiff.ts +182 -0
- package/src/utils/remediationCommand.test.ts +163 -0
- package/src/utils/remediationCommand.ts +119 -0
- package/src/utils/retry.test.ts +153 -0
- package/src/utils/retry.ts +58 -0
- package/src/utils/reviewCleanup.ts +106 -0
- package/src/utils/run.ts +99 -0
- package/src/utils/runContext.ts +145 -0
- package/src/utils/runContextData.ts +58 -0
- package/src/utils/runErrorRenderer.test.ts +95 -0
- package/src/utils/runErrorRenderer.ts +259 -0
- package/src/utils/runFixture.ts +76 -0
- package/src/utils/runLifecycle.ts +237 -0
- package/src/utils/runStartupLog.ts +60 -0
- package/src/utils/secrets.test.ts +103 -0
- package/src/utils/secrets.ts +177 -0
- package/src/utils/setup.test.ts +509 -0
- package/src/utils/setup.ts +352 -0
- package/src/utils/shell.ts +103 -0
- package/src/utils/skills.test.ts +46 -0
- package/src/utils/skills.ts +67 -0
- package/src/utils/subprocess.test.ts +170 -0
- package/src/utils/subprocess.ts +438 -0
- package/src/utils/terraformMcp.test.ts +63 -0
- package/src/utils/terraformMcp.ts +83 -0
- package/src/utils/time.test.ts +105 -0
- package/src/utils/time.ts +59 -0
- package/src/utils/timer.test.ts +91 -0
- package/src/utils/timer.ts +72 -0
- package/src/utils/todoTracking.test.ts +223 -0
- package/src/utils/todoTracking.ts +167 -0
- package/src/utils/token.test.ts +239 -0
- package/src/utils/token.ts +186 -0
- package/src/utils/version.ts +10 -0
- package/src/utils/versioning.test.ts +34 -0
- package/src/utils/versioning.ts +44 -0
- package/src/utils/vertex.ts +85 -0
- package/src/utils/workflow.ts +25 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source for the opencode plugin we drop into the per-run tmpdir at
|
|
3
|
+
* `<XDG_CONFIG_HOME>/opencode/plugin/terramend-events.ts`. The harness already
|
|
4
|
+
* redirects `XDG_CONFIG_HOME` to `ctx.tmpdir/.config` (see `opencode.ts`
|
|
5
|
+
* `homeEnv`), so opencode's auto-discovery scans the tmpdir, never the user's
|
|
6
|
+
* working tree. opencode's `Global.Path.config` resolves to
|
|
7
|
+
* `path.join(xdgConfig, "opencode")` and the config layer auto-discovers
|
|
8
|
+
* plugins from every directory in its scan list — including
|
|
9
|
+
* `Global.Path.config` — by globbing `{plugin,plugins}/*.{ts,js}` via
|
|
10
|
+
* `ConfigPlugin.load(dir)`.
|
|
11
|
+
*
|
|
12
|
+
* We MUST NOT write into the user's repo working tree. The repo is a checkout
|
|
13
|
+
* the agent operates on; only the agent's own tools (gated by
|
|
14
|
+
* `OPENCODE_PERMISSION`) may modify it. The whole reason we redirect HOME and
|
|
15
|
+
* XDG_CONFIG_HOME is so harness-side files (config, plugins, scratch state)
|
|
16
|
+
* land in the tmpdir.
|
|
17
|
+
*
|
|
18
|
+
* Why the events plugin exists: opencode's `task` tool runs subagents
|
|
19
|
+
* in-process and the CLI's `cli/cmd/run.ts` event loop filters
|
|
20
|
+
* `part.sessionID !== sessionID`, so subagent-internal `message.part.updated`
|
|
21
|
+
* events are silently discarded before reaching our parent NDJSON stream.
|
|
22
|
+
* plugins, by contrast, receive EVERY bus event via `bus.subscribeAll()`
|
|
23
|
+
* regardless of session.
|
|
24
|
+
*
|
|
25
|
+
* The events plugin re-emits every relevant bus event onto opencode's stdout
|
|
26
|
+
* as a single JSON line wrapped in a sentinel envelope. our `runOpenCode`
|
|
27
|
+
* parser recognises the envelope, unpacks it, and routes the inner part
|
|
28
|
+
* through the existing handlers with a per-session label from `SessionLabeler`
|
|
29
|
+
* so each subagent's tool calls / text appear inline alongside the
|
|
30
|
+
* orchestrator's.
|
|
31
|
+
*
|
|
32
|
+
* The subagent gate (the `tool.execute.before` hook that hard-blocks
|
|
33
|
+
* state-mutating MCP tool calls from a subagent session) lives in a SEPARATE
|
|
34
|
+
* plugin — `TERRAMEND_OPENCODE_GATE_PLUGIN_SOURCE` below — because it's the
|
|
35
|
+
* load-bearing security fence and must ship into the opencode harness,
|
|
36
|
+
* whereas this events re-emitter was only needed by the legacy CLI-parsing
|
|
37
|
+
* path (the active `opencode.ts` reads subagent events directly off the SDK
|
|
38
|
+
* event stream, so it installs ONLY the gate plugin). Deny-list source of
|
|
39
|
+
* truth: `src/agents/subagentToolGates.ts`.
|
|
40
|
+
*
|
|
41
|
+
* Dumb plugin / smart parent split: the events plugin emits every part for
|
|
42
|
+
* every session. the parent dedupes against the orchestrator's own session id
|
|
43
|
+
* (which it already knows from the `init` event). this keeps the plugin trivial
|
|
44
|
+
* and keeps the per-session attribution logic on the parent side where the
|
|
45
|
+
* SessionLabeler already lives.
|
|
46
|
+
*
|
|
47
|
+
* Event-name prefixing: the wrapped event-type sentinel is
|
|
48
|
+
* `terramend_bus_event` — picked to be unmistakably ours so a future opencode
|
|
49
|
+
* release that introduces a coincidentally-named event type won't collide.
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
import { SUBAGENT_DENIED_TOOLS } from "#app/agents/subagentToolGates";
|
|
53
|
+
|
|
54
|
+
export const TERRAMEND_BUS_EVENT_TYPE = "terramend_bus_event" as const;
|
|
55
|
+
|
|
56
|
+
export const TERRAMEND_OPENCODE_PLUGIN_FILENAME = "terramend-events.ts" as const;
|
|
57
|
+
|
|
58
|
+
export const TERRAMEND_OPENCODE_GATE_PLUGIN_FILENAME = "terramend-subagent-gate.ts" as const;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Source written verbatim to `<XDG_CONFIG_HOME>/opencode/plugin/terramend-events.ts`.
|
|
62
|
+
*
|
|
63
|
+
* - Structural typing only (no runtime import of `@opencode-ai/plugin`):
|
|
64
|
+
* opencode installs that dep into the directory containing the plugin
|
|
65
|
+
* alongside discovery, but a) the dep isn't required for the structural
|
|
66
|
+
* shape we use, and b) keeping zero imports avoids any module-resolution
|
|
67
|
+
* coupling to opencode's plugin-loader internals across versions.
|
|
68
|
+
* - default export is the plugin factory (opencode's plugin loader accepts
|
|
69
|
+
* default exports as the server entrypoint).
|
|
70
|
+
* - we only forward `message.part.updated`. that's where the user-visible
|
|
71
|
+
* subagent activity (tool calls, text, step transitions) lives. add more
|
|
72
|
+
* event types here if the parent needs them.
|
|
73
|
+
* - JSON.stringify+single write keeps the line atomic up to PIPE_BUF (4KB on
|
|
74
|
+
* Linux). longer parts may interleave with concurrent stdout writers; the
|
|
75
|
+
* parser tolerates non-JSON lines (logs them at debug) so a torn line is a
|
|
76
|
+
* missed event, not a crash.
|
|
77
|
+
*/
|
|
78
|
+
export const TERRAMEND_OPENCODE_PLUGIN_SOURCE = `// AUTOGENERATED by Terramend. do not edit; it'll be overwritten on the next run.
|
|
79
|
+
// surfaces opencode subagent activity that the CLI's run-loop discards. see
|
|
80
|
+
// action/agents/opencodePlugin.ts in terramend/app for why this exists. lives
|
|
81
|
+
// inside the per-run tmpdir (XDG_CONFIG_HOME/opencode/plugin/), never inside
|
|
82
|
+
// the user's working tree. the subagent gate ships as a separate plugin
|
|
83
|
+
// (terramend-subagent-gate.ts).
|
|
84
|
+
|
|
85
|
+
const TERRAMEND_BUS_EVENT_TYPE = ${JSON.stringify(TERRAMEND_BUS_EVENT_TYPE)};
|
|
86
|
+
|
|
87
|
+
// orchestratorSessionID locks to the first sessionID we observe on the bus
|
|
88
|
+
// (\`message.part.updated\`). opencode's run command creates exactly one
|
|
89
|
+
// top-level session before any subagent is dispatched, and the orchestrator
|
|
90
|
+
// emits at least one event before any subagent does — so first-seen-wins
|
|
91
|
+
// captures the orchestrator; every subagent part has a non-matching
|
|
92
|
+
// sessionID.
|
|
93
|
+
let orchestratorSessionID: string | undefined;
|
|
94
|
+
|
|
95
|
+
function isOrchestratorTaskDispatch(part: {
|
|
96
|
+
type?: string;
|
|
97
|
+
tool?: string;
|
|
98
|
+
state?: { status?: string };
|
|
99
|
+
} | undefined): boolean {
|
|
100
|
+
if (!part || part.type !== "tool") return false;
|
|
101
|
+
if (part.tool !== "task") return false;
|
|
102
|
+
// only forward at status="running" (not "pending"). at pending the
|
|
103
|
+
// state.input is still {} — the orchestrator has emitted the part shell
|
|
104
|
+
// but the LLM hasn't filled in description/subagent_type/prompt yet. by
|
|
105
|
+
// running, input is populated and recordTaskDispatch can derive the lens
|
|
106
|
+
// label correctly.
|
|
107
|
+
return part.state?.status === "running";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export default async function terramendEventsPlugin() {
|
|
111
|
+
return {
|
|
112
|
+
event: async (input: {
|
|
113
|
+
event: {
|
|
114
|
+
type: string;
|
|
115
|
+
properties?: {
|
|
116
|
+
part?: {
|
|
117
|
+
sessionID?: string;
|
|
118
|
+
type?: string;
|
|
119
|
+
tool?: string;
|
|
120
|
+
state?: { status?: string };
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
}) => {
|
|
125
|
+
const event = input?.event;
|
|
126
|
+
if (!event || typeof event !== "object") return;
|
|
127
|
+
if (event.type !== "message.part.updated") return;
|
|
128
|
+
const part = event.properties?.part;
|
|
129
|
+
const sessionID = part?.sessionID;
|
|
130
|
+
if (typeof sessionID !== "string" || sessionID.length === 0) return;
|
|
131
|
+
if (orchestratorSessionID === undefined) orchestratorSessionID = sessionID;
|
|
132
|
+
|
|
133
|
+
if (sessionID === orchestratorSessionID) {
|
|
134
|
+
// skip orchestrator events EXCEPT early task dispatches.
|
|
135
|
+
if (!isOrchestratorTaskDispatch(part)) return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const line = JSON.stringify({
|
|
140
|
+
type: TERRAMEND_BUS_EVENT_TYPE,
|
|
141
|
+
bus_event: event,
|
|
142
|
+
});
|
|
143
|
+
process.stdout.write(line + "\\n");
|
|
144
|
+
} catch {
|
|
145
|
+
// a circular reference or BigInt etc. would throw; swallow rather
|
|
146
|
+
// than letting a single bad event take down the plugin.
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
`;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Standalone subagent gate plugin written to
|
|
155
|
+
* `<XDG_CONFIG_HOME>/opencode/plugin/terramend-subagent-gate.ts`. Installed by
|
|
156
|
+
* the in-process `opencode.ts` harness — the gate is the load-bearing security
|
|
157
|
+
* fence, so it ships independently of the events re-emitter above (which the
|
|
158
|
+
* in-process harness doesn't need).
|
|
159
|
+
*
|
|
160
|
+
* Hard-blocks state-mutating MCP tool calls originating from a subagent
|
|
161
|
+
* session via `tool.execute.before`, complementing the runtime backstops from
|
|
162
|
+
* PR #796 (action/mcp/checkout.ts, action/mcp/git.ts). Deny-list source of
|
|
163
|
+
* truth: `action/agents/subagentToolGates.ts`.
|
|
164
|
+
*/
|
|
165
|
+
export const TERRAMEND_OPENCODE_GATE_PLUGIN_SOURCE = `// AUTOGENERATED by Terramend. do not edit; it'll be overwritten on the next run.
|
|
166
|
+
// hard-blocks state-mutating MCP tool calls from opencode subagents (PR
|
|
167
|
+
// following terramend/app#796 — see action/agents/subagentToolGates.ts). see
|
|
168
|
+
// action/agents/opencodePlugin.ts in terramend/app for why this exists. lives
|
|
169
|
+
// inside the per-run tmpdir (XDG_CONFIG_HOME/opencode/plugin/), never inside
|
|
170
|
+
// the user's working tree.
|
|
171
|
+
|
|
172
|
+
// embedded at template-render time so the plugin source has no imports.
|
|
173
|
+
// kept in sync with action/agents/subagentToolGates.ts via a single re-export.
|
|
174
|
+
const SUBAGENT_DENIED_TOOLS = new Set(${JSON.stringify(SUBAGENT_DENIED_TOOLS)});
|
|
175
|
+
|
|
176
|
+
// opencode presents MCP tools as \`terramend_<bare>\`; strip that to compare
|
|
177
|
+
// against the canonical bare names in SUBAGENT_DENIED_TOOLS. native tools
|
|
178
|
+
// (read, write, bash, task, etc.) lack the prefix and never match the deny
|
|
179
|
+
// list anyway.
|
|
180
|
+
function stripTerramendPrefix(toolName: string): string {
|
|
181
|
+
if (toolName.startsWith("terramend_")) return toolName.slice("terramend_".length);
|
|
182
|
+
return toolName;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// orchestratorSessionID locks to the first sessionID we observe on
|
|
186
|
+
// \`tool.execute.before\`. the orchestrator must invoke a tool (e.g. its
|
|
187
|
+
// \`task\` dispatch) before any subagent session exists, so first-seen-wins
|
|
188
|
+
// always captures the orchestrator; every subagent call therefore has a
|
|
189
|
+
// non-matching sessionID and is subject to the deny check.
|
|
190
|
+
let orchestratorSessionID: string | undefined;
|
|
191
|
+
|
|
192
|
+
export default async function terramendSubagentGatePlugin() {
|
|
193
|
+
return {
|
|
194
|
+
"tool.execute.before": async (
|
|
195
|
+
input: { tool?: string; sessionID?: string; callID?: string },
|
|
196
|
+
_output: { args?: Record<string, unknown> }
|
|
197
|
+
) => {
|
|
198
|
+
// input: { tool, sessionID, callID } — confirmed against
|
|
199
|
+
// anomalyco/opencode packages/opencode/src/session/tools.ts which
|
|
200
|
+
// calls plugin.trigger("tool.execute.before", { tool, sessionID,
|
|
201
|
+
// callID }, { args }). throwing here surfaces as a tool error to the
|
|
202
|
+
// model (Effect's run.promise propagates the rejection through the
|
|
203
|
+
// AI SDK's tool-runtime), so the subagent sees a denial and can
|
|
204
|
+
// pick a different action.
|
|
205
|
+
const sessionID = typeof input?.sessionID === "string" ? input.sessionID : "";
|
|
206
|
+
const tool = typeof input?.tool === "string" ? input.tool : "";
|
|
207
|
+
if (!sessionID || !tool) return;
|
|
208
|
+
if (orchestratorSessionID === undefined) orchestratorSessionID = sessionID;
|
|
209
|
+
if (sessionID === orchestratorSessionID) return;
|
|
210
|
+
const bare = stripTerramendPrefix(tool);
|
|
211
|
+
if (!SUBAGENT_DENIED_TOOLS.has(bare)) return;
|
|
212
|
+
throw new Error(
|
|
213
|
+
"subagent attempted to call denied tool '" + bare + "'. " +
|
|
214
|
+
"subagents share the orchestrator's in-process working tree and toolState; " +
|
|
215
|
+
"state-changing MCP tools (checkout_pr, push_branch, create_pull_request_review, " +
|
|
216
|
+
"report_progress, etc.) are reserved for the orchestrator. " +
|
|
217
|
+
"report findings back to the orchestrator and let it perform the mutation."
|
|
218
|
+
);
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
`;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { geminiHighThinkingOverrides } from "#app/agents/opencodeShared";
|
|
3
|
+
import { modelAliases } from "#app/models";
|
|
4
|
+
|
|
5
|
+
describe("geminiHighThinkingOverrides", () => {
|
|
6
|
+
// Expected truth pulled the same way the helper does — both must derive from
|
|
7
|
+
// the registry so the test exercises the wiring, not a hand-maintained list.
|
|
8
|
+
const expectedApiIds = modelAliases
|
|
9
|
+
.filter((a) => a.provider === "google")
|
|
10
|
+
.map((a) => a.resolve.replace(/^google\//, ""));
|
|
11
|
+
const overrides = geminiHighThinkingOverrides();
|
|
12
|
+
|
|
13
|
+
it("covers every direct-Google alias in the registry", () => {
|
|
14
|
+
expect(Object.keys(overrides).sort()).toEqual([...expectedApiIds].sort());
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("is non-empty (catches accidental whole-provider removal)", () => {
|
|
18
|
+
expect(Object.keys(overrides).length).toBeGreaterThan(0);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("strips the `google/` prefix from each resolve to get the bare API id", () => {
|
|
22
|
+
for (const id of Object.keys(overrides)) {
|
|
23
|
+
expect(id).not.toMatch(/^google\//);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("pins every entry to thinkingLevel: high", () => {
|
|
28
|
+
for (const [id, value] of Object.entries(overrides)) {
|
|
29
|
+
expect(value, `entry for ${id}`).toEqual({
|
|
30
|
+
options: { thinkingConfig: { thinkingLevel: "high" } },
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Shared helpers for the OpenCode agent harness (`./opencode.ts`). Pure config
|
|
2
|
+
// / model-registry / install glue — nothing here touches the SDK event loop.
|
|
3
|
+
// Kept as a separate module so this config surface stays unit-testable in
|
|
4
|
+
// isolation (see `./opencodeShared.test.ts`).
|
|
5
|
+
|
|
6
|
+
import { REVIEWER_AGENT_NAME, REVIEWER_SYSTEM_PROMPT } from "#app/agents/reviewer";
|
|
7
|
+
import { deriveSubagentModels } from "#app/agents/subagentModels";
|
|
8
|
+
import { modelAliases } from "#app/models";
|
|
9
|
+
import { log } from "#app/utils/cli";
|
|
10
|
+
import { installFromNpmTarball } from "#app/utils/install";
|
|
11
|
+
import { getAuthorizedModels } from "#app/utils/openCodeModels";
|
|
12
|
+
import { getDevDependencyVersion } from "#app/utils/version";
|
|
13
|
+
|
|
14
|
+
// ── config ─────────────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
export type OpenCodeConfig = {
|
|
17
|
+
mcp?: Record<string, unknown>;
|
|
18
|
+
permission?: Record<string, unknown>;
|
|
19
|
+
provider?: Record<string, unknown>;
|
|
20
|
+
agent?: Record<string, unknown>;
|
|
21
|
+
experimental?: Record<string, unknown>;
|
|
22
|
+
model?: string;
|
|
23
|
+
enabled_providers?: string[];
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Build the `provider.google.models[id].options` map that pins every direct-Google
|
|
29
|
+
* Gemini alias to `thinkingLevel: "high"`. Sourced from the model registry so
|
|
30
|
+
* adding/renaming a Google alias in `action/models.ts` flows through automatically.
|
|
31
|
+
*/
|
|
32
|
+
export function geminiHighThinkingOverrides(): Record<string, { options: object }> {
|
|
33
|
+
return Object.fromEntries(
|
|
34
|
+
modelAliases
|
|
35
|
+
.filter((a) => a.provider === "google")
|
|
36
|
+
.map((a) => [
|
|
37
|
+
a.resolve.replace(/^google\//, ""),
|
|
38
|
+
{ options: { thinkingConfig: { thinkingLevel: "high" } } },
|
|
39
|
+
]),
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Read-only `reviewfrog` subagent for lens-based review. Non-mutative +
|
|
45
|
+
* non-recursive — enforced by the system prompt in reviewer.ts.
|
|
46
|
+
*
|
|
47
|
+
* Per-subagent `model:` override is driven by the registry in
|
|
48
|
+
* `action/models.ts` via each alias's `subagentModel` field. Currently wired:
|
|
49
|
+
* Anthropic opus → sonnet, OpenAI gpt-pro → gpt and gpt → gpt-5.4, Google
|
|
50
|
+
* gemini-pro → gemini-flash. Other providers inherit (no override).
|
|
51
|
+
*/
|
|
52
|
+
export function buildReviewerAgentConfig(
|
|
53
|
+
orchestratorModel: string | undefined,
|
|
54
|
+
): Record<string, unknown> {
|
|
55
|
+
const overrides = deriveSubagentModels(orchestratorModel);
|
|
56
|
+
return {
|
|
57
|
+
[REVIEWER_AGENT_NAME]: {
|
|
58
|
+
description:
|
|
59
|
+
"Read-only review subagent for lens-based code review (correctness, security, billing-subsystem, etc.). " +
|
|
60
|
+
"Reads only — no writes, no state-changing shell or MCP calls, no nested subagent dispatch.",
|
|
61
|
+
mode: "subagent",
|
|
62
|
+
prompt: REVIEWER_SYSTEM_PROMPT,
|
|
63
|
+
...(overrides.reviewer !== undefined ? { model: overrides.reviewer } : {}),
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ── install ────────────────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Install the opencode-ai npm tarball and return the path to the executable.
|
|
72
|
+
*
|
|
73
|
+
* The bin path differs by version: v1.4.x and earlier shipped `bin/opencode`;
|
|
74
|
+
* v1.14+ renames the platform-specific binary to `bin/opencode.exe` for every
|
|
75
|
+
* OS via the postinstall script. Callers pass the binPath that matches their
|
|
76
|
+
* pinned version so a v1↔v2 swap can't silently install the wrong file.
|
|
77
|
+
*/
|
|
78
|
+
export async function installOpencodeCli(params: { binPath: string }): Promise<string> {
|
|
79
|
+
return await installFromNpmTarball({
|
|
80
|
+
packageName: "opencode-ai",
|
|
81
|
+
version: getDevDependencyVersion("opencode-ai"),
|
|
82
|
+
executablePath: params.binPath,
|
|
83
|
+
installDependencies: true,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── model auto-select fallback ──────────────────────────────────────────────────
|
|
88
|
+
//
|
|
89
|
+
// steps 1–2 of model resolution (TERRAMEND_MODEL env, slug resolution) happen
|
|
90
|
+
// in resolveModel() in utils/agent.ts before the agent runs. this is step 3:
|
|
91
|
+
// auto-select using the authorized model set captured in main.ts via
|
|
92
|
+
// `opencode models` introspection.
|
|
93
|
+
|
|
94
|
+
const AUTO_SELECT_WARNING =
|
|
95
|
+
"select a model explicitly in the Terramend console (https://terramend.com/console) to avoid this.";
|
|
96
|
+
|
|
97
|
+
export function autoSelectModel(): string | undefined {
|
|
98
|
+
const authorized = getAuthorizedModels();
|
|
99
|
+
if (authorized.size > 0) {
|
|
100
|
+
// skip hidden aliases (internal subagent-tier targets like
|
|
101
|
+
// opencode/gpt-5.4) — they should never surface as a user-facing
|
|
102
|
+
// orchestrator pick. mirrors the selectable-list filter in
|
|
103
|
+
// components/ModelSelector.tsx.
|
|
104
|
+
const match =
|
|
105
|
+
modelAliases.find((a) => !a.hidden && a.preferred && authorized.has(a.resolve)) ??
|
|
106
|
+
modelAliases.find((a) => !a.hidden && authorized.has(a.resolve));
|
|
107
|
+
if (match) {
|
|
108
|
+
log.info(
|
|
109
|
+
`» model: ${match.resolve} (auto-selected${match.preferred ? " — preferred" : ""} curated match)`,
|
|
110
|
+
);
|
|
111
|
+
log.warning(`» model auto-selected. ${AUTO_SELECT_WARNING}`);
|
|
112
|
+
return match.resolve;
|
|
113
|
+
}
|
|
114
|
+
log.info(
|
|
115
|
+
`» opencode has ${authorized.size} models but none match curated aliases — letting OpenCode auto-select`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
log.warning(`» no model resolved. letting OpenCode auto-select. ${AUTO_SELECT_WARNING}`);
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|