@defrex/autobuild 0.8.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 +201 -0
- package/README.md +192 -0
- package/SPEC.md +2269 -0
- package/bin/ab-build-runner.ts +47 -0
- package/bin/ab-dev.ts +27 -0
- package/bin/ab-dispatch-kernel.ts +92 -0
- package/bin/ab-harvest-runner.ts +29 -0
- package/bin/ab-init-probe.ts +19 -0
- package/bin/ab-process-group-reaper.ts +32 -0
- package/bin/ab.ts +6 -0
- package/bin/agent/ab +3 -0
- package/docs/answering-blocked-builds.md +89 -0
- package/docs/architecture.md +726 -0
- package/docs/assets/headline-wide.png +0 -0
- package/docs/configuration.md +1623 -0
- package/docs/hosted-dispatcher-cutover-validation.md +178 -0
- package/docs/hosted-dispatcher.md +204 -0
- package/docs/mcp.md +119 -0
- package/docs/operator-api.md +186 -0
- package/docs/remote-store-protocol.md +972 -0
- package/docs/remote-ticket-protocol.md +79 -0
- package/docs/setup.md +289 -0
- package/docs/spec-standard.md +63 -0
- package/docs/ticket-dependencies.md +59 -0
- package/docs/vercel-sandbox-rollout-probe.md +7 -0
- package/package.json +135 -0
- package/packages/core/src/cli/args.ts +74 -0
- package/packages/core/src/cli/artifact.ts +182 -0
- package/packages/core/src/cli/binary.ts +198 -0
- package/packages/core/src/cli/build-control.ts +789 -0
- package/packages/core/src/cli/build-progress.ts +55 -0
- package/packages/core/src/cli/bulk-control.ts +385 -0
- package/packages/core/src/cli/context.ts +470 -0
- package/packages/core/src/cli/dashboard/actions.ts +59 -0
- package/packages/core/src/cli/dashboard/cells.ts +92 -0
- package/packages/core/src/cli/dashboard/composer.ts +230 -0
- package/packages/core/src/cli/dashboard/detail.ts +100 -0
- package/packages/core/src/cli/dashboard/frame-image.ts +373 -0
- package/packages/core/src/cli/dashboard/live.ts +152 -0
- package/packages/core/src/cli/dashboard/model.ts +1173 -0
- package/packages/core/src/cli/dashboard/poll.ts +196 -0
- package/packages/core/src/cli/dashboard/render.ts +1568 -0
- package/packages/core/src/cli/dashboard/selection.ts +66 -0
- package/packages/core/src/cli/dashboard/session-feed.ts +244 -0
- package/packages/core/src/cli/dashboard/session-view.ts +506 -0
- package/packages/core/src/cli/dashboard/transcript.ts +108 -0
- package/packages/core/src/cli/dev.ts +46 -0
- package/packages/core/src/cli/dispatch-frontend.ts +1045 -0
- package/packages/core/src/cli/dispatch-process.ts +259 -0
- package/packages/core/src/cli/dispatch.ts +3692 -0
- package/packages/core/src/cli/dotenv.ts +48 -0
- package/packages/core/src/cli/env.ts +206 -0
- package/packages/core/src/cli/harvest.ts +698 -0
- package/packages/core/src/cli/help.ts +713 -0
- package/packages/core/src/cli/init-agent.ts +106 -0
- package/packages/core/src/cli/init-validation.ts +583 -0
- package/packages/core/src/cli/init.ts +756 -0
- package/packages/core/src/cli/installation.ts +333 -0
- package/packages/core/src/cli/keyboard.ts +261 -0
- package/packages/core/src/cli/main.ts +1399 -0
- package/packages/core/src/cli/mcp.ts +279 -0
- package/packages/core/src/cli/models.ts +65 -0
- package/packages/core/src/cli/observe.ts +55 -0
- package/packages/core/src/cli/plugin.ts +227 -0
- package/packages/core/src/cli/pr-attachments.ts +138 -0
- package/packages/core/src/cli/pr-summary.ts +124 -0
- package/packages/core/src/cli/publication-settlement.ts +188 -0
- package/packages/core/src/cli/repo-state.ts +213 -0
- package/packages/core/src/cli/repository-status.ts +80 -0
- package/packages/core/src/cli/self-update.ts +443 -0
- package/packages/core/src/cli/status.ts +895 -0
- package/packages/core/src/cli/store-opening.contract.ts +289 -0
- package/packages/core/src/cli/store-opening.ts +167 -0
- package/packages/core/src/cli/store-ref.ts +33 -0
- package/packages/core/src/cli/terminal-restore.ts +174 -0
- package/packages/core/src/cli/terminal.ts +361 -0
- package/packages/core/src/cli/terminals.ts +995 -0
- package/packages/core/src/cli/testkit.ts +194 -0
- package/packages/core/src/cli/ticket.ts +484 -0
- package/packages/core/src/cli/upgrade-agent.ts +180 -0
- package/packages/core/src/cli/upgrade-commit.ts +468 -0
- package/packages/core/src/cli/upgrade-notice.ts +91 -0
- package/packages/core/src/cli/upgrade-progress.ts +110 -0
- package/packages/core/src/cli/upgrade.ts +888 -0
- package/packages/core/src/cli/wait.ts +587 -0
- package/packages/core/src/cli/watch.ts +932 -0
- package/packages/core/src/config/live.ts +255 -0
- package/packages/core/src/config/load.ts +98 -0
- package/packages/core/src/config/roles.ts +438 -0
- package/packages/core/src/config/schema.ts +774 -0
- package/packages/core/src/distribution.ts +24 -0
- package/packages/core/src/events/catalog.ts +201 -0
- package/packages/core/src/events/envelope.ts +59 -0
- package/packages/core/src/events/payloads.ts +618 -0
- package/packages/core/src/events/repository.ts +559 -0
- package/packages/core/src/events/sessions.ts +180 -0
- package/packages/core/src/harvest/schema.ts +147 -0
- package/packages/core/src/ids.ts +45 -0
- package/packages/core/src/integration/harness.ts +681 -0
- package/packages/core/src/kernel/auto-merge.ts +259 -0
- package/packages/core/src/kernel/converge.ts +117 -0
- package/packages/core/src/kernel/dispatch-settings.ts +60 -0
- package/packages/core/src/kernel/dispatch-status.ts +141 -0
- package/packages/core/src/kernel/engine.ts +1047 -0
- package/packages/core/src/kernel/harvest.ts +823 -0
- package/packages/core/src/kernel/origin.ts +69 -0
- package/packages/core/src/kernel/phases.ts +196 -0
- package/packages/core/src/kernel/plan-verify-selection.ts +105 -0
- package/packages/core/src/kernel/pr-attachments.ts +82 -0
- package/packages/core/src/kernel/reducer.ts +752 -0
- package/packages/core/src/kernel/stall.ts +155 -0
- package/packages/core/src/kernel/verify-applicability.ts +52 -0
- package/packages/core/src/markdown.ts +67 -0
- package/packages/core/src/ontology.ts +288 -0
- package/packages/core/src/open-map.ts +203 -0
- package/packages/core/src/operator/annotations.ts +187 -0
- package/packages/core/src/operator/client.ts +311 -0
- package/packages/core/src/operator/control.ts +117 -0
- package/packages/core/src/operator/index.ts +62 -0
- package/packages/core/src/operator/presentation.ts +22 -0
- package/packages/core/src/operator/protocol.ts +98 -0
- package/packages/core/src/operator/query.ts +201 -0
- package/packages/core/src/operator/registry.ts +1043 -0
- package/packages/core/src/operator/requests.ts +138 -0
- package/packages/core/src/operator/sandbox.ts +458 -0
- package/packages/core/src/operator/server.ts +616 -0
- package/packages/core/src/operator/sessions.ts +223 -0
- package/packages/core/src/operator/tickets.ts +177 -0
- package/packages/core/src/plugin-sdk/index.ts +174 -0
- package/packages/core/src/plugins/contract-entry.ts +74 -0
- package/packages/core/src/plugins/contract-env.ts +3 -0
- package/packages/core/src/plugins/load.ts +259 -0
- package/packages/core/src/plugins/manifest.ts +236 -0
- package/packages/core/src/plugins/registry.ts +274 -0
- package/packages/core/src/plugins/runtimes.ts +75 -0
- package/packages/core/src/ports/forge/contract.ts +258 -0
- package/packages/core/src/ports/forge/create.ts +74 -0
- package/packages/core/src/ports/forge/fake.ts +488 -0
- package/packages/core/src/ports/forge/github-pr-attachments.ts +351 -0
- package/packages/core/src/ports/forge/github-transport.ts +352 -0
- package/packages/core/src/ports/forge/github.ts +1162 -0
- package/packages/core/src/ports/forge/local-git.ts +568 -0
- package/packages/core/src/ports/runner/claude.ts +873 -0
- package/packages/core/src/ports/runner/cli-stream.ts +90 -0
- package/packages/core/src/ports/runner/codex.ts +873 -0
- package/packages/core/src/ports/runner/contract.ts +492 -0
- package/packages/core/src/ports/runner/fake.ts +213 -0
- package/packages/core/src/ports/runner/json-record.ts +2 -0
- package/packages/core/src/ports/runner/live-contract-fixture.ts +176 -0
- package/packages/core/src/ports/runner/one-shot.ts +29 -0
- package/packages/core/src/ports/runner/pi-bridge-core.ts +136 -0
- package/packages/core/src/ports/runner/pi-bridge.js +6 -0
- package/packages/core/src/ports/runner/pi-rpc.ts +454 -0
- package/packages/core/src/ports/runner/pi.ts +617 -0
- package/packages/core/src/ports/runner/production.ts +74 -0
- package/packages/core/src/ports/runner/provider-error.ts +121 -0
- package/packages/core/src/ports/runner/routing.ts +234 -0
- package/packages/core/src/ports/runner/runtime.ts +214 -0
- package/packages/core/src/ports/runner/session-env.ts +34 -0
- package/packages/core/src/ports/runner/stream-parts.ts +103 -0
- package/packages/core/src/ports/tickets/contract.ts +570 -0
- package/packages/core/src/ports/tickets/create.ts +135 -0
- package/packages/core/src/ports/tickets/fake.ts +215 -0
- package/packages/core/src/ports/tickets/file.ts +559 -0
- package/packages/core/src/ports/tickets/linear.ts +811 -0
- package/packages/core/src/ports/tickets/operations.ts +161 -0
- package/packages/core/src/ports/tickets/remote-index.ts +13 -0
- package/packages/core/src/ports/tickets/remote-protocol.ts +80 -0
- package/packages/core/src/ports/tickets/remote-server.ts +185 -0
- package/packages/core/src/ports/tickets/remote.ts +130 -0
- package/packages/core/src/ports/tickets/update.ts +51 -0
- package/packages/core/src/ports/types.ts +528 -0
- package/packages/core/src/ports/workspace/build-execution.ts +123 -0
- package/packages/core/src/ports/workspace/contract.ts +136 -0
- package/packages/core/src/ports/workspace/create.ts +132 -0
- package/packages/core/src/ports/workspace/distribution-archive.ts +317 -0
- package/packages/core/src/ports/workspace/fake.ts +432 -0
- package/packages/core/src/ports/workspace/git-worktree.ts +848 -0
- package/packages/core/src/ports/workspace/harvest-execution.ts +111 -0
- package/packages/core/src/ports/workspace/in-process-build-execution.ts +30 -0
- package/packages/core/src/ports/workspace/local-build-execution.ts +88 -0
- package/packages/core/src/ports/workspace/operator-sandbox.ts +142 -0
- package/packages/core/src/ports/workspace/phase-scratch.ts +156 -0
- package/packages/core/src/ports/workspace/vercel-sandbox.ts +2541 -0
- package/packages/core/src/processes/build-child-exit.ts +78 -0
- package/packages/core/src/processes/build-child.ts +119 -0
- package/packages/core/src/processes/build-execution-state.ts +95 -0
- package/packages/core/src/processes/build-parent-watch.ts +35 -0
- package/packages/core/src/processes/build-runner.ts +2614 -0
- package/packages/core/src/processes/dispatcher.ts +2825 -0
- package/packages/core/src/processes/execution-settlement.ts +171 -0
- package/packages/core/src/processes/harvest-child.ts +115 -0
- package/packages/core/src/processes/harvest-execution-state.ts +121 -0
- package/packages/core/src/processes/harvest-runner.ts +1656 -0
- package/packages/core/src/processes/harvest.ts +595 -0
- package/packages/core/src/processes/infrastructure-failure-budget.ts +146 -0
- package/packages/core/src/processes/phase-failure-budget.ts +52 -0
- package/packages/core/src/processes/process-group-reaper.ts +35 -0
- package/packages/core/src/processes/process-group.ts +65 -0
- package/packages/core/src/processes/publication-loss.ts +62 -0
- package/packages/core/src/processes/publication-state.ts +93 -0
- package/packages/core/src/processes/sandbox-state.ts +101 -0
- package/packages/core/src/registry.ts +24 -0
- package/packages/core/src/sessions/reducer.ts +185 -0
- package/packages/core/src/skills.ts +7 -0
- package/packages/core/src/spec-standard.ts +47 -0
- package/packages/core/src/store/adapter.ts +80 -0
- package/packages/core/src/store/build-scope.ts +272 -0
- package/packages/core/src/store/contract.ts +2581 -0
- package/packages/core/src/store/local/blobs.ts +38 -0
- package/packages/core/src/store/local/schema.ts +180 -0
- package/packages/core/src/store/local/store.ts +1713 -0
- package/packages/core/src/store/memory.ts +1138 -0
- package/packages/core/src/store/phase-session.ts +425 -0
- package/packages/core/src/store/remote/client.ts +780 -0
- package/packages/core/src/store/remote/index.ts +12 -0
- package/packages/core/src/store/remote/protocol.ts +348 -0
- package/packages/core/src/store/remote/server.ts +923 -0
- package/packages/core/src/store/remote/token.ts +137 -0
- package/packages/core/src/store/remote/version.ts +10 -0
- package/packages/core/src/store/retention.ts +91 -0
- package/packages/core/src/store/session-handle.ts +265 -0
- package/packages/core/src/store/streams/assemble.ts +79 -0
- package/packages/core/src/store/streams/lock.ts +42 -0
- package/packages/core/src/store/streams/session-writer.ts +306 -0
- package/packages/core/src/store/streams/types.ts +171 -0
- package/packages/core/src/store/streams/wait.ts +127 -0
- package/packages/core/src/store/subscribe.ts +105 -0
- package/packages/core/src/store/types.ts +466 -0
- package/packages/core/src/testing/fixed.ts +44 -0
- package/packages/core/src/testing/store-failures.ts +32 -0
- package/packages/core/src/zod-issues.ts +91 -0
- package/skills/code-review/SKILL.md +100 -0
- package/skills/finalize/SKILL.md +44 -0
- package/skills/guide/SKILL.md +1781 -0
- package/skills/guide/references/operator-api.md +164 -0
- package/skills/guide/references/plugin-authoring.md +354 -0
- package/skills/guide/references/remote-store-protocol.md +972 -0
- package/skills/guide/references/remote-ticket-protocol.md +79 -0
- package/skills/guide/references/setup.md +289 -0
- package/skills/guide/references/spec-standard.md +63 -0
- package/skills/guide/references/ticket-dependencies.md +59 -0
- package/skills/harvest/SKILL.md +66 -0
- package/skills/harvest-review/SKILL.md +63 -0
- package/skills/implement/SKILL.md +82 -0
- package/skills/plan/SKILL.md +123 -0
- package/skills/plan-review/SKILL.md +121 -0
- package/skills/reconcile/SKILL.md +65 -0
- package/skills/spec/SKILL.md +132 -0
- package/skills/tickets/SKILL.md +139 -0
- package/templates/autobuild.toml +36 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { AgentTurnFailure } from '../types'
|
|
2
|
+
|
|
3
|
+
/** Optional structured signals exposed by a runtime in addition to its error
|
|
4
|
+
* text. Unknown hints are deliberately treated as availability failures: that
|
|
5
|
+
* preserves the existing bounded retry policy and permits an explicitly
|
|
6
|
+
* configured alternate without hiding credential evidence. */
|
|
7
|
+
export interface ProviderErrorHints {
|
|
8
|
+
status?: number | null
|
|
9
|
+
codes?: readonly (string | number | null | undefined)[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const EXHAUSTION_CODE_PATTERNS = [
|
|
13
|
+
/(?:^|[_-])(?:billing|payment|quota|credit|balance|budget)(?:[_-]|$)/,
|
|
14
|
+
/^(?:insufficient|out_of)_(?:quota|credits?|balance|funds)$/,
|
|
15
|
+
/^usage_(?:limit|quota)(?:_|$)/,
|
|
16
|
+
] as const
|
|
17
|
+
|
|
18
|
+
const CREDENTIAL_CODE_PATTERNS = [
|
|
19
|
+
/^(?:auth|authentication|authorization)(?:_|-)/,
|
|
20
|
+
/^(?:unauthorized|forbidden)$/,
|
|
21
|
+
/^(?:invalid|expired|missing|revoked)_(?:api_)?(?:key|token|credential)s?$/,
|
|
22
|
+
/^(?:permission|access)(?:_|-)(?:denied|error|required)$/,
|
|
23
|
+
/^oauth_org_not_allowed$/,
|
|
24
|
+
] as const
|
|
25
|
+
|
|
26
|
+
const EXHAUSTION_TEXT_PATTERNS = [
|
|
27
|
+
/(?:^|[^a-z0-9])(?:quota(?:[_ -](?:exceeded|exhausted|depleted|refreshes?))?|(?:insufficient|exhausted|depleted)[_ -]quota)(?:$|[^a-z0-9])/i,
|
|
28
|
+
/\busage[_ -]?(?:limit|quota)\b/i,
|
|
29
|
+
/\b(?:billing|spending|monthly)\s+(?:limit|cap|cycle)\b/i,
|
|
30
|
+
/\bbilling(?:_error|\s+(?:error|issue|required|disabled))\b/i,
|
|
31
|
+
/\bpayment\s+(?:is\s+)?required\b/i,
|
|
32
|
+
/\bsubscription\s+(?:is\s+)?(?:expired|inactive|required|disabled)\b/i,
|
|
33
|
+
/\b(?:insufficient|no|out of|exhausted|depleted)\s+(?:credits?|balance|funds)\b/i,
|
|
34
|
+
/\bcredits?\s+(?:are\s+|is\s+)?(?:required|exhausted|depleted)\b/i,
|
|
35
|
+
/\bbudget\s+(?:is\s+)?(?:exceeded|exhausted|depleted)\b/i,
|
|
36
|
+
] as const
|
|
37
|
+
|
|
38
|
+
const CREDENTIAL_TEXT_PATTERNS = [
|
|
39
|
+
/\b(?:authentication|authorization)[_ -](?:failed|required|error|invalid)\b/i,
|
|
40
|
+
/\bunauthori[sz]ed\b/i,
|
|
41
|
+
/\bforbidden\b/i,
|
|
42
|
+
/\b(?:invalid|expired|missing|revoked)[_ -](?:api[_ -]?)?(?:key|token|credentials?)\b/i,
|
|
43
|
+
/\b(?:api[- ]?key|token|credentials?)\s+(?:is\s+|are\s+)?(?:invalid|expired|missing|revoked)\b/i,
|
|
44
|
+
/\bpermission(?:_error|\s+(?:denied|required|error))\b/i,
|
|
45
|
+
/\b(?:do not|does not|don't|doesn't)\s+have\s+permission\b/i,
|
|
46
|
+
/\b(?:access|request)\s+(?:is\s+)?(?:denied|not permitted)\b/i,
|
|
47
|
+
] as const
|
|
48
|
+
|
|
49
|
+
/** Classify a provider/SDK-declared turn error without rewriting its message.
|
|
50
|
+
* Exhaustion evidence intentionally wins over generic 402/403 and permission
|
|
51
|
+
* evidence: providers commonly wrap usage-limit responses in those shapes. */
|
|
52
|
+
export function classifyProviderError(
|
|
53
|
+
message: string,
|
|
54
|
+
hints: ProviderErrorHints = {},
|
|
55
|
+
): AgentTurnFailure {
|
|
56
|
+
const codes = hints.codes ?? []
|
|
57
|
+
const exhaustion =
|
|
58
|
+
hints.status === 402 ||
|
|
59
|
+
codes.some((code) => code === 402 || matchesCode(code, EXHAUSTION_CODE_PATTERNS)) ||
|
|
60
|
+
hasHttpStatus(message, [402]) ||
|
|
61
|
+
EXHAUSTION_TEXT_PATTERNS.some((pattern) => pattern.test(message))
|
|
62
|
+
if (exhaustion) return { message, permanent: true, cause: 'exhaustion' }
|
|
63
|
+
|
|
64
|
+
const credentials =
|
|
65
|
+
hints.status === 401 ||
|
|
66
|
+
hints.status === 403 ||
|
|
67
|
+
codes.some(
|
|
68
|
+
(code) => code === 401 || code === 403 || matchesCode(code, CREDENTIAL_CODE_PATTERNS),
|
|
69
|
+
) ||
|
|
70
|
+
hasHttpStatus(message, [401, 403]) ||
|
|
71
|
+
CREDENTIAL_TEXT_PATTERNS.some((pattern) => pattern.test(message))
|
|
72
|
+
if (credentials) return { message, permanent: true, cause: 'credentials' }
|
|
73
|
+
|
|
74
|
+
return { message, permanent: false, cause: 'availability' }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Whether another declared target may be tried inside this phase attempt.
|
|
78
|
+
* Legacy plugins have no cause: preserve their old safe split. */
|
|
79
|
+
export function isAlternateEligible(failure: AgentTurnFailure): boolean {
|
|
80
|
+
return failure.cause === undefined
|
|
81
|
+
? !failure.permanent
|
|
82
|
+
: failure.cause === 'availability' || failure.cause === 'exhaustion'
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Final-failure retry behavior remains governed by the compatibility bit. */
|
|
86
|
+
export function mayRetryPhase(failure: AgentTurnFailure): boolean {
|
|
87
|
+
return !failure.permanent
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Explicit local configuration failures bypass both alternates and phase retries. */
|
|
91
|
+
export function configurationFailure(message: string): AgentTurnFailure {
|
|
92
|
+
return { message, permanent: true, cause: 'configuration' }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Explicit credential failures bypass both alternates and phase retries. */
|
|
96
|
+
export function credentialFailure(message: string): AgentTurnFailure {
|
|
97
|
+
return { message, permanent: true, cause: 'credentials' }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function matchesCode(
|
|
101
|
+
code: string | number | null | undefined,
|
|
102
|
+
patterns: readonly RegExp[],
|
|
103
|
+
): boolean {
|
|
104
|
+
if (code === null || code === undefined || typeof code === 'number') return false
|
|
105
|
+
const normalized = code.trim().toLowerCase()
|
|
106
|
+
return patterns.some((pattern) => pattern.test(normalized))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function hasHttpStatus(message: string, statuses: readonly number[]): boolean {
|
|
110
|
+
const alternatives = statuses.join('|')
|
|
111
|
+
return (
|
|
112
|
+
new RegExp(
|
|
113
|
+
`^\\s*(?:(?:http(?:\\/\\d(?:\\.\\d)?)?|status(?:\\s+code)?)\\s*[:=]?\\s*)?(?:${alternatives})\\b`,
|
|
114
|
+
'i',
|
|
115
|
+
).test(message) ||
|
|
116
|
+
new RegExp(`\\bhttp(?:\\s+status)?\\s*[:=]?\\s*(?:${alternatives})\\b`, 'i').test(message) ||
|
|
117
|
+
new RegExp(
|
|
118
|
+
`["']?(?:status|status_code|statusCode)["']?\\s*[:=]\\s*["']?(?:${alternatives})\\b`,
|
|
119
|
+
).test(message)
|
|
120
|
+
)
|
|
121
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Eager runtime/model/args role resolution (SPEC §9, §16.1). Primary
|
|
3
|
+
* fields and the ordered alternate list inherit independently from the
|
|
4
|
+
* reserved `[roles.default]` entry. A declared role list, including `[]`,
|
|
5
|
+
* replaces the inherited list wholesale; each entry overlays that concrete
|
|
6
|
+
* role's effective primary axes and is validated at construction.
|
|
7
|
+
*/
|
|
8
|
+
import type { AgentRunner } from '../types'
|
|
9
|
+
import { serves, type RuntimeRegistration, type RuntimeRegistry } from './runtime'
|
|
10
|
+
|
|
11
|
+
/** A resolved runtime/model pair plus the adapter to run it (§9). */
|
|
12
|
+
export interface ResolvedRuntime {
|
|
13
|
+
runner: AgentRunner
|
|
14
|
+
/** The registry key == the frozen `session.started.runner` value. */
|
|
15
|
+
runtime: string
|
|
16
|
+
/** Absent ⇒ the adapter's built-in default model. */
|
|
17
|
+
model?: string
|
|
18
|
+
/** Extra CLI argv tokens, preserved in declared order. */
|
|
19
|
+
args: readonly string[]
|
|
20
|
+
/** Session-stream capability passthrough from the registration (SPEC §9).
|
|
21
|
+
* Absent ⇒ this runtime's sessions get no stream. */
|
|
22
|
+
openSessionStream?: RuntimeRegistration['openSessionStream']
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** A role's primary target plus its failure-triggered targets in declaration order. */
|
|
26
|
+
export interface ResolvedRole extends ResolvedRuntime {
|
|
27
|
+
/** Wall-clock budget shared by the logical role's primary and alternates. */
|
|
28
|
+
sessionBudgetSeconds: number
|
|
29
|
+
alternates: readonly ResolvedRuntime[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface RuntimeAxes {
|
|
33
|
+
runtime?: string
|
|
34
|
+
model?: string
|
|
35
|
+
args?: readonly string[]
|
|
36
|
+
/** Deprecated compatibility field; accepted by the schema but ignored. */
|
|
37
|
+
extensions?: readonly string[]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** One role entry as it arrives from `[roles]`: all fields are optional. */
|
|
41
|
+
export interface RuntimeSpec extends RuntimeAxes {
|
|
42
|
+
/** Inherits from [roles.default], then the policy fallback. */
|
|
43
|
+
sessionBudgetSeconds?: number
|
|
44
|
+
alternates?: readonly RuntimeAxes[]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class RuntimeConfigError extends Error {
|
|
48
|
+
constructor(readonly problems: string[]) {
|
|
49
|
+
super(
|
|
50
|
+
`invalid runtime/model/args configuration (SPEC §9):\n` +
|
|
51
|
+
problems.map((p) => ` - ${p}`).join('\n'),
|
|
52
|
+
)
|
|
53
|
+
this.name = 'RuntimeConfigError'
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface RuntimeResolver {
|
|
58
|
+
/** Resolve the earliest declared role/alias, otherwise `[roles.default]`. */
|
|
59
|
+
resolve(role: string, ...aliases: string[]): ResolvedRole
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function runtimeNames(registry: RuntimeRegistry): string {
|
|
63
|
+
return Object.keys(registry).join(', ') || 'none'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The declared model families for one runtime, for error messages. */
|
|
67
|
+
function servedModels(registration: RuntimeRegistration): string {
|
|
68
|
+
return registration.servesModels.join(', ') || 'no models'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function mergeAxes(spec: RuntimeAxes, base: RuntimeAxes): RuntimeAxes {
|
|
72
|
+
const runtime = spec.runtime ?? base.runtime
|
|
73
|
+
const model = spec.model ?? base.model
|
|
74
|
+
return {
|
|
75
|
+
...(runtime !== undefined ? { runtime } : {}),
|
|
76
|
+
...(model !== undefined ? { model } : {}),
|
|
77
|
+
args: spec.args ?? base.args ?? [],
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Resolve one already-merged raw target. Registry defaults apply only when no
|
|
82
|
+
* configured model exists on that target. Problems aggregate instead of throw. */
|
|
83
|
+
function resolveAxes(
|
|
84
|
+
axes: RuntimeAxes,
|
|
85
|
+
registry: RuntimeRegistry,
|
|
86
|
+
label: string,
|
|
87
|
+
problems: string[],
|
|
88
|
+
): ResolvedRuntime | undefined {
|
|
89
|
+
const runtime = axes.runtime
|
|
90
|
+
if (runtime === undefined || runtime.trim().length === 0) {
|
|
91
|
+
problems.push(
|
|
92
|
+
`${label} is missing required runtime. Add:\n\n` +
|
|
93
|
+
`[roles.default]\n` +
|
|
94
|
+
`runtime = "<runtime>"\n\n` +
|
|
95
|
+
`Available runtimes: ${runtimeNames(registry)}`,
|
|
96
|
+
)
|
|
97
|
+
return undefined
|
|
98
|
+
}
|
|
99
|
+
if (!Object.hasOwn(registry, runtime)) {
|
|
100
|
+
problems.push(
|
|
101
|
+
`${label} resolves to runtime "${runtime}", which is not registered ` +
|
|
102
|
+
`(registered runtimes: ${runtimeNames(registry)})`,
|
|
103
|
+
)
|
|
104
|
+
return undefined
|
|
105
|
+
}
|
|
106
|
+
const reg = registry[runtime]!
|
|
107
|
+
|
|
108
|
+
const model = axes.model ?? reg.defaultModel
|
|
109
|
+
const modelValid = model === undefined || serves(reg, model)
|
|
110
|
+
if (!modelValid) {
|
|
111
|
+
problems.push(
|
|
112
|
+
`${label} resolves runtime "${runtime}" with model "${model}", but ` +
|
|
113
|
+
`"${runtime}" serves only [${servedModels(reg)}]`,
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const args = axes.args ?? []
|
|
118
|
+
const ownedArgs = new Set(reg.ownedArgs ?? [])
|
|
119
|
+
for (const arg of args) {
|
|
120
|
+
if (arg === reg.promptBoundary) {
|
|
121
|
+
problems.push(
|
|
122
|
+
`${label} argument ${JSON.stringify(arg)} conflicts with the prompt boundary for runtime "${runtime}". ` +
|
|
123
|
+
'Remove it because Autobuild owns and appends this prompt separator.',
|
|
124
|
+
)
|
|
125
|
+
continue
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const spelling = arg.startsWith('--') ? arg.split('=', 1)[0]! : arg
|
|
129
|
+
const owned =
|
|
130
|
+
ownedArgs.has(spelling) ||
|
|
131
|
+
[...ownedArgs].some(
|
|
132
|
+
(candidate) =>
|
|
133
|
+
candidate.startsWith('-') &&
|
|
134
|
+
!candidate.startsWith('--') &&
|
|
135
|
+
candidate.length === 2 &&
|
|
136
|
+
arg.startsWith(candidate) &&
|
|
137
|
+
arg.length > candidate.length,
|
|
138
|
+
)
|
|
139
|
+
if (owned) {
|
|
140
|
+
problems.push(
|
|
141
|
+
`${label} argument ${JSON.stringify(arg)} conflicts with an option owned by runtime "${runtime}"`,
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!modelValid) return undefined
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
runner: reg.runner,
|
|
150
|
+
runtime,
|
|
151
|
+
...(model !== undefined ? { model } : {}),
|
|
152
|
+
args,
|
|
153
|
+
...(reg.openSessionStream !== undefined ? { openSessionStream: reg.openSessionStream } : {}),
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function resolveRole(
|
|
158
|
+
spec: RuntimeSpec,
|
|
159
|
+
defaultSpec: RuntimeSpec,
|
|
160
|
+
policySessionBudgetSeconds: number,
|
|
161
|
+
registry: RuntimeRegistry,
|
|
162
|
+
label: string,
|
|
163
|
+
problems: string[],
|
|
164
|
+
): ResolvedRole | undefined {
|
|
165
|
+
const primaryAxes = mergeAxes(spec, defaultSpec)
|
|
166
|
+
const primary = resolveAxes(primaryAxes, registry, label, problems)
|
|
167
|
+
const alternateSpecs = spec.alternates ?? defaultSpec.alternates ?? []
|
|
168
|
+
const alternates: ResolvedRuntime[] = []
|
|
169
|
+
alternateSpecs.forEach((alternate, index) => {
|
|
170
|
+
const resolved = resolveAxes(
|
|
171
|
+
mergeAxes(alternate, primaryAxes),
|
|
172
|
+
registry,
|
|
173
|
+
`${label}.alternates[${index}]`,
|
|
174
|
+
problems,
|
|
175
|
+
)
|
|
176
|
+
if (resolved !== undefined) alternates.push(resolved)
|
|
177
|
+
})
|
|
178
|
+
return primary === undefined
|
|
179
|
+
? undefined
|
|
180
|
+
: {
|
|
181
|
+
...primary,
|
|
182
|
+
sessionBudgetSeconds:
|
|
183
|
+
spec.sessionBudgetSeconds ??
|
|
184
|
+
defaultSpec.sessionBudgetSeconds ??
|
|
185
|
+
policySessionBudgetSeconds,
|
|
186
|
+
alternates,
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Build and eagerly validate the complete role resolver. */
|
|
191
|
+
export function createRuntimeResolver(
|
|
192
|
+
registry: RuntimeRegistry,
|
|
193
|
+
roles: Record<string, RuntimeSpec>,
|
|
194
|
+
policySessionBudgetSeconds = 3600,
|
|
195
|
+
): RuntimeResolver {
|
|
196
|
+
const problems: string[] = []
|
|
197
|
+
const defaultSpec = roles.default ?? {}
|
|
198
|
+
const resolvedDefault = resolveRole(
|
|
199
|
+
defaultSpec,
|
|
200
|
+
{},
|
|
201
|
+
policySessionBudgetSeconds,
|
|
202
|
+
registry,
|
|
203
|
+
'[roles.default]',
|
|
204
|
+
problems,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
const resolvedRoles: Record<string, ResolvedRole> = Object.create(null)
|
|
208
|
+
for (const [role, spec] of Object.entries(roles)) {
|
|
209
|
+
if (role === 'default') continue
|
|
210
|
+
const resolved = resolveRole(
|
|
211
|
+
spec,
|
|
212
|
+
defaultSpec,
|
|
213
|
+
policySessionBudgetSeconds,
|
|
214
|
+
registry,
|
|
215
|
+
`[roles.${role}]`,
|
|
216
|
+
problems,
|
|
217
|
+
)
|
|
218
|
+
if (resolved !== undefined) resolvedRoles[role] = resolved
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (problems.length > 0) throw new RuntimeConfigError(problems)
|
|
222
|
+
const fallback = resolvedDefault!
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
resolve(role: string, ...aliases: string[]): ResolvedRole {
|
|
226
|
+
for (const key of [role, ...aliases]) {
|
|
227
|
+
if (key === 'default') return fallback
|
|
228
|
+
const resolved = resolvedRoles[key]
|
|
229
|
+
if (resolved !== undefined) return resolved
|
|
230
|
+
}
|
|
231
|
+
return fallback
|
|
232
|
+
},
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runtime registry (SPEC §9): the "adapter registry" the spec says adding
|
|
3
|
+
* a runtime should touch — and ONLY it. Each registration pairs an AgentRunner
|
|
4
|
+
* adapter with its capabilities: model families/default for compatibility
|
|
5
|
+
* validation, plus an
|
|
6
|
+
* optional tool-free, non-phase one-shot completion. Keeping capabilities HERE, in a
|
|
7
|
+
* wrapper around the adapter, is deliberate: the `AgentRunner` port itself is
|
|
8
|
+
* frozen (spec "out of scope" — no renaming/extending it), so capability data
|
|
9
|
+
* lives beside the registry rather than inside the port. The kernel never
|
|
10
|
+
* learns a runtime's name; it asks the resolver, and the resolver reads this.
|
|
11
|
+
*
|
|
12
|
+
* "runtime" is the config-surface vocabulary for what the code has always
|
|
13
|
+
* called a runner: the registry KEY is the runtime name (== the frozen
|
|
14
|
+
* `session.started.runner` value the build-runner records), while the adapter's
|
|
15
|
+
* own `.name` is a separate, also-frozen thing that fills
|
|
16
|
+
* `AgentSessionHandle.runner`.
|
|
17
|
+
*/
|
|
18
|
+
import type { AgentRunner, SessionStreamSink } from '../types'
|
|
19
|
+
import type { OneShotCompletion } from './one-shot'
|
|
20
|
+
|
|
21
|
+
/** Bracket metadata for a session stream's `data-ab-session` part. */
|
|
22
|
+
export interface SessionStreamInfo {
|
|
23
|
+
session: string
|
|
24
|
+
role: string
|
|
25
|
+
runner: string
|
|
26
|
+
model?: string
|
|
27
|
+
phase: string
|
|
28
|
+
round?: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface RuntimeUsabilityInput {
|
|
32
|
+
cwd: string
|
|
33
|
+
env: Readonly<Record<string, string | undefined>>
|
|
34
|
+
/** Exact configured models the prospective init profile will write. */
|
|
35
|
+
models: readonly string[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface RuntimeUsabilityDiagnostic {
|
|
39
|
+
usable: boolean
|
|
40
|
+
/** Human-readable prerequisite/auth result. Must not contain credentials. */
|
|
41
|
+
reason: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Boolean remains accepted for backward compatibility with plugin probes. */
|
|
45
|
+
export type RuntimeUsabilityResult = boolean | RuntimeUsabilityDiagnostic
|
|
46
|
+
|
|
47
|
+
/** Optional onboarding probe. Absence means init must not suggest the runtime. */
|
|
48
|
+
export type RuntimeUsabilityProbe = (
|
|
49
|
+
input: RuntimeUsabilityInput,
|
|
50
|
+
) => Promise<RuntimeUsabilityResult>
|
|
51
|
+
|
|
52
|
+
export interface RuntimeRegistration {
|
|
53
|
+
/** The adapter behind this runtime (§9). */
|
|
54
|
+
runner: AgentRunner
|
|
55
|
+
/**
|
|
56
|
+
* Optional tool-free, non-resumable, non-phase completion capability. Its
|
|
57
|
+
* absence is a normal capability boundary, not a configuration error;
|
|
58
|
+
* deterministic callers decide their own fail-safe fallback.
|
|
59
|
+
*/
|
|
60
|
+
oneShot?: OneShotCompletion
|
|
61
|
+
/**
|
|
62
|
+
* Optional session-stream capability (SPEC §9): given a bracket-scoped
|
|
63
|
+
* sink, create the runtime's one stream for this session bracket and
|
|
64
|
+
* return its store id (or `undefined` to decline streaming for the
|
|
65
|
+
* bracket). Its absence means the runtime emits no parts and its sessions
|
|
66
|
+
* have no stream — a supported boundary, not a configuration error.
|
|
67
|
+
*/
|
|
68
|
+
openSessionStream?: (
|
|
69
|
+
sink: SessionStreamSink,
|
|
70
|
+
info: SessionStreamInfo,
|
|
71
|
+
) => Promise<string | undefined>
|
|
72
|
+
/** Runtime-local prerequisite/auth check used only by `ab init`. */
|
|
73
|
+
initUsable?: RuntimeUsabilityProbe
|
|
74
|
+
/**
|
|
75
|
+
* Model-id PREFIXES this runtime can serve, e.g. `['openai/',
|
|
76
|
+
* 'kimi-coding/']`. Prefix families — not an exhaustive id list — because the
|
|
77
|
+
* model landscape moves faster than the pipeline and the spec forbids
|
|
78
|
+
* hardcoding served ids: a new model from either provider validates without
|
|
79
|
+
* editing this list. These families are validation data only: role resolution
|
|
80
|
+
* never searches them to choose or substitute a runtime. `serves()` below is
|
|
81
|
+
* the matcher.
|
|
82
|
+
*/
|
|
83
|
+
servesModels: string[]
|
|
84
|
+
/**
|
|
85
|
+
* This runtime's own default model, used when a step selects the runtime but
|
|
86
|
+
* names no model. When set, it must match `servesModels` like any other
|
|
87
|
+
* resolved model. `undefined` ⇒ the adapter's un-named built-in default (e.g.
|
|
88
|
+
* Claude's subscription default) — the path that preserves no-model behavior.
|
|
89
|
+
*/
|
|
90
|
+
defaultModel?: string
|
|
91
|
+
/** Model-selection and parsed protocol/output-mode spellings owned by the
|
|
92
|
+
* adapter, plus documented aliases of those options. Every other role arg
|
|
93
|
+
* remains freeform, including options the adapter may also emit. */
|
|
94
|
+
ownedArgs?: readonly string[]
|
|
95
|
+
/**
|
|
96
|
+
* Exact structural token Autobuild appends before the positional prompt.
|
|
97
|
+
* Role args may not duplicate this boundary.
|
|
98
|
+
*/
|
|
99
|
+
promptBoundary?: string
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** name → registration. The one place a runtime's name lives (§9). */
|
|
103
|
+
export type RuntimeRegistry = Record<string, RuntimeRegistration>
|
|
104
|
+
|
|
105
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
106
|
+
return typeof value === 'object' && value !== null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Validate an untrusted plugin factory result at the composition boundary.
|
|
111
|
+
* TypeScript's structural types disappear at runtime, so factories must not be
|
|
112
|
+
* able to smuggle a partial adapter or inconsistent capability declaration
|
|
113
|
+
* into the eager role resolver.
|
|
114
|
+
*/
|
|
115
|
+
export function validateRuntimeRegistration(value: unknown): RuntimeRegistration {
|
|
116
|
+
if (!isObject(value)) {
|
|
117
|
+
throw new Error('must be an object')
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const runner = value.runner
|
|
121
|
+
if (!isObject(runner)) {
|
|
122
|
+
throw new Error('runner must be an AgentRunner-shaped object')
|
|
123
|
+
}
|
|
124
|
+
if (typeof runner.name !== 'string' || runner.name.trim().length === 0) {
|
|
125
|
+
throw new Error('runner.name must be a nonblank string')
|
|
126
|
+
}
|
|
127
|
+
for (const method of ['start', 'continue', 'end'] as const) {
|
|
128
|
+
if (typeof runner[method] !== 'function') {
|
|
129
|
+
throw new Error(`runner.${method} must be a function`)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const servesModels = value.servesModels
|
|
134
|
+
if (
|
|
135
|
+
!Array.isArray(servesModels) ||
|
|
136
|
+
servesModels.some(
|
|
137
|
+
(family) =>
|
|
138
|
+
typeof family !== 'string' ||
|
|
139
|
+
family.trim().length === 0 ||
|
|
140
|
+
(family.includes('*') && family !== '*/*'),
|
|
141
|
+
)
|
|
142
|
+
) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
'servesModels must be an array of nonblank strings containing prefixes or the provider-qualified wildcard "*/*"',
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const defaultModel = value.defaultModel
|
|
149
|
+
if (
|
|
150
|
+
defaultModel !== undefined &&
|
|
151
|
+
(typeof defaultModel !== 'string' || defaultModel.trim().length === 0)
|
|
152
|
+
) {
|
|
153
|
+
throw new Error('defaultModel must be a nonblank string when provided')
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const ownedArgs = value.ownedArgs
|
|
157
|
+
if (
|
|
158
|
+
ownedArgs !== undefined &&
|
|
159
|
+
(!Array.isArray(ownedArgs) ||
|
|
160
|
+
ownedArgs.some(
|
|
161
|
+
(arg) => typeof arg !== 'string' || !arg.startsWith('-') || arg.trim().length === 0,
|
|
162
|
+
) ||
|
|
163
|
+
new Set(ownedArgs).size !== ownedArgs.length)
|
|
164
|
+
) {
|
|
165
|
+
throw new Error('ownedArgs must be a unique array of nonblank option spellings')
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const promptBoundary = value.promptBoundary
|
|
169
|
+
if (
|
|
170
|
+
promptBoundary !== undefined &&
|
|
171
|
+
(typeof promptBoundary !== 'string' || promptBoundary.trim().length === 0)
|
|
172
|
+
) {
|
|
173
|
+
throw new Error('promptBoundary must be a nonblank string when provided')
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const oneShot = value.oneShot
|
|
177
|
+
if (oneShot !== undefined && (!isObject(oneShot) || typeof oneShot.complete !== 'function')) {
|
|
178
|
+
throw new Error('oneShot.complete must be a function when provided')
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (value.openSessionStream !== undefined && typeof value.openSessionStream !== 'function') {
|
|
182
|
+
throw new Error('openSessionStream must be a function when provided')
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (value.initUsable !== undefined && typeof value.initUsable !== 'function') {
|
|
186
|
+
throw new Error('initUsable must be a function when provided')
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const registration = value as unknown as RuntimeRegistration
|
|
190
|
+
if (typeof defaultModel === 'string' && !serves(registration, defaultModel)) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`defaultModel "${defaultModel}" is not served by servesModels ` +
|
|
193
|
+
`[${servesModels.join(', ')}]`,
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
return registration
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Does this registration serve `model`? Prefix-family match:
|
|
201
|
+
* `openai/gpt-5.6-sol` is served by a registration declaring `openai/`. Empty
|
|
202
|
+
* `servesModels` serves nothing (a runtime that can run only with its adapter's
|
|
203
|
+
* un-named built-in default, never with a configured model id).
|
|
204
|
+
*/
|
|
205
|
+
export function serves(reg: RuntimeRegistration, model: string): boolean {
|
|
206
|
+
return reg.servesModels.some((family) =>
|
|
207
|
+
family === '*/*' ? isProviderQualified(model) : model.startsWith(family),
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function isProviderQualified(model: string): boolean {
|
|
212
|
+
const slash = model.indexOf('/')
|
|
213
|
+
return slash > 0 && slash < model.length - 1
|
|
214
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the environment passed to an agent runtime for one turn.
|
|
3
|
+
*
|
|
4
|
+
* Scoped values (AB_STORE, AB_PHASE, and so on) override the ambient process
|
|
5
|
+
* environment. The distribution-owned launcher is then forced to the front of
|
|
6
|
+
* PATH so an inherited executable with the public `ab` name cannot shadow the
|
|
7
|
+
* Autobuild CLI. A fresh object is returned on every call; neither input nor
|
|
8
|
+
* process.env is mutated, which keeps concurrent builds isolated.
|
|
9
|
+
*/
|
|
10
|
+
import { delimiter } from 'node:path'
|
|
11
|
+
import { distributionPath } from '../../distribution'
|
|
12
|
+
|
|
13
|
+
/** Private command directory shipped by this Autobuild distribution. */
|
|
14
|
+
export const AGENT_BIN_DIR = distributionPath('bin', 'agent')
|
|
15
|
+
|
|
16
|
+
export function sessionEnv(
|
|
17
|
+
scoped: Readonly<Record<string, string>>,
|
|
18
|
+
ambient: Readonly<Record<string, string | undefined>> = process.env,
|
|
19
|
+
): Record<string, string> {
|
|
20
|
+
const env: Record<string, string> = {}
|
|
21
|
+
for (const [key, value] of Object.entries(ambient)) {
|
|
22
|
+
if (value !== undefined) env[key] = value
|
|
23
|
+
}
|
|
24
|
+
Object.assign(env, scoped)
|
|
25
|
+
|
|
26
|
+
const inheritedPath = env.PATH
|
|
27
|
+
const inheritedEntries =
|
|
28
|
+
inheritedPath === undefined || inheritedPath === ''
|
|
29
|
+
? []
|
|
30
|
+
: inheritedPath.split(delimiter).filter((entry) => entry !== AGENT_BIN_DIR)
|
|
31
|
+
env.PATH = [AGENT_BIN_DIR, ...inheritedEntries].join(delimiter)
|
|
32
|
+
|
|
33
|
+
return env
|
|
34
|
+
}
|