@defrex/autobuild 0.11.0 → 0.13.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/README.md +3 -1
- package/SPEC.md +335 -31
- package/docs/architecture.md +125 -7
- package/docs/configuration.md +142 -21
- package/docs/hosted-dispatcher.md +17 -2
- package/docs/hosted-service.md +240 -0
- package/docs/mcp.md +8 -2
- package/docs/operator-api.md +97 -7
- package/docs/remote-store-protocol.md +199 -144
- package/docs/remote-ticket-protocol.md +249 -19
- package/docs/setup.md +199 -16
- package/docs/ticket-dependencies.md +1 -1
- package/docs/ticket-migration.md +64 -0
- package/package.json +7 -6
- package/packages/core/src/cli/attention.ts +209 -0
- package/packages/core/src/cli/build-control.ts +12 -1
- package/packages/core/src/cli/configured-store.ts +17 -0
- package/packages/core/src/cli/connect.ts +364 -0
- package/packages/core/src/cli/context.ts +7 -1
- package/packages/core/src/cli/dashboard/model.ts +4 -5
- package/packages/core/src/cli/dashboard/poll.ts +1 -1
- package/packages/core/src/cli/dispatch.ts +336 -41
- package/packages/core/src/cli/harvest.ts +13 -3
- package/packages/core/src/cli/help.ts +51 -16
- package/packages/core/src/cli/init-validation.ts +27 -2
- package/packages/core/src/cli/installation.ts +67 -13
- package/packages/core/src/cli/main.ts +60 -16
- package/packages/core/src/cli/mcp.ts +9 -2
- package/packages/core/src/cli/pr-summary-publication.ts +220 -0
- package/packages/core/src/cli/pr-summary.ts +2 -0
- package/packages/core/src/cli/project-credentials.ts +132 -0
- package/packages/core/src/cli/repo-state.ts +15 -2
- package/packages/core/src/cli/self-update.ts +15 -1
- package/packages/core/src/cli/store-opening.ts +22 -14
- package/packages/core/src/cli/terminals.ts +0 -10
- package/packages/core/src/cli/ticket-assets.ts +7 -2
- package/packages/core/src/cli/ticket-editor.ts +82 -0
- package/packages/core/src/cli/ticket.ts +172 -35
- package/packages/core/src/cli/upgrade-compatibility.ts +80 -0
- package/packages/core/src/cli/wait.ts +84 -17
- package/packages/core/src/cli/watch.ts +197 -60
- package/packages/core/src/config/live.ts +3 -0
- package/packages/core/src/config/schema.ts +90 -11
- package/packages/core/src/connection.ts +86 -0
- package/packages/core/src/events/catalog.ts +11 -2
- package/packages/core/src/events/payloads.ts +50 -0
- package/packages/core/src/events/repository.ts +13 -0
- package/packages/core/src/integration/harness.ts +37 -2
- package/packages/core/src/kernel/__snapshots__/reducer-registry.test.ts.snap +145 -83
- package/packages/core/src/kernel/engine.ts +24 -0
- package/packages/core/src/kernel/generators/build-log.ts +11 -1
- package/packages/core/src/kernel/incremental-fixtures.ts +13 -2
- package/packages/core/src/kernel/log-index.ts +17 -0
- package/packages/core/src/kernel/pr-summary.ts +89 -0
- package/packages/core/src/kernel/reducer-registry.ts +10 -0
- package/packages/core/src/kernel/reducer.ts +8 -0
- package/packages/core/src/ontology.ts +2 -0
- package/packages/core/src/operator/annotations.ts +12 -0
- package/packages/core/src/operator/index.ts +12 -0
- package/packages/core/src/operator/protocol.ts +19 -2
- package/packages/core/src/operator/query.ts +38 -16
- package/packages/core/src/operator/registry.ts +61 -8
- package/packages/core/src/operator/ticket-source-backend.ts +17 -20
- package/packages/core/src/operator/tickets.ts +136 -28
- package/packages/core/src/plugin-sdk/index.ts +7 -0
- package/packages/core/src/plugins/manifest.ts +5 -3
- package/packages/core/src/plugins/runtimes.ts +7 -1
- package/packages/core/src/ports/forge/fake.ts +13 -1
- package/packages/core/src/ports/forge/github-transport.ts +36 -1
- package/packages/core/src/ports/forge/github.ts +29 -5
- package/packages/core/src/ports/forge/local-git.ts +12 -4
- package/packages/core/src/ports/forge/summary-marker.ts +8 -0
- package/packages/core/src/ports/runner/session-env.ts +3 -0
- package/packages/core/src/ports/tickets/contract.ts +66 -1
- package/packages/core/src/ports/tickets/create.ts +54 -10
- package/packages/core/src/ports/tickets/fake.ts +30 -1
- package/packages/core/src/ports/tickets/file-assets.ts +159 -0
- package/packages/core/src/ports/tickets/file-codec.ts +90 -0
- package/packages/core/src/ports/tickets/file.ts +114 -120
- package/packages/core/src/ports/tickets/grants.ts +47 -0
- package/packages/core/src/ports/tickets/hosted-index.ts +17 -3
- package/packages/core/src/ports/tickets/identity.ts +20 -0
- package/packages/core/src/ports/tickets/lifecycle.ts +18 -0
- package/packages/core/src/ports/tickets/linear.ts +74 -9
- package/packages/core/src/ports/tickets/migration.ts +336 -0
- package/packages/core/src/ports/tickets/operations.ts +38 -7
- package/packages/core/src/ports/tickets/portable-assets.ts +54 -0
- package/packages/core/src/ports/tickets/remote-protocol.ts +96 -0
- package/packages/core/src/ports/tickets/remote.ts +61 -3
- package/packages/core/src/ports/tickets/requested-id.ts +46 -0
- package/packages/core/src/ports/tickets/revision.ts +62 -0
- package/packages/core/src/ports/tickets/states.ts +61 -0
- package/packages/core/src/ports/types.ts +40 -3
- package/packages/core/src/ports/workspace/build-execution.ts +6 -1
- package/packages/core/src/ports/workspace/create.ts +15 -2
- package/packages/core/src/ports/workspace/distribution-archive.ts +3 -5
- package/packages/core/src/ports/workspace/harvest-execution.ts +3 -1
- package/packages/core/src/ports/workspace/local-build-execution.ts +8 -1
- package/packages/core/src/ports/workspace/operator-sandbox.ts +2 -0
- package/packages/core/src/ports/workspace/vercel-sandbox.ts +51 -19
- package/packages/core/src/processes/build-execution-state.ts +1 -1
- package/packages/core/src/processes/build-runner.ts +87 -26
- package/packages/core/src/processes/dispatch-failure-budget.ts +83 -0
- package/packages/core/src/processes/dispatcher.ts +168 -41
- package/packages/core/src/processes/execution-no-progress.ts +176 -0
- package/packages/core/src/processes/harvest-child.ts +31 -4
- package/packages/core/src/processes/harvest-execution-state.ts +19 -2
- package/packages/core/src/processes/harvest-runner.ts +166 -104
- package/packages/core/src/processes/harvest.ts +34 -14
- package/packages/core/src/processes/phase-failure-budget.ts +2 -0
- package/packages/core/src/processes/publication-loss.ts +18 -1
- package/packages/core/src/processes/repo-view.ts +35 -0
- package/packages/core/src/store/contract.ts +32 -1
- package/packages/core/src/store/local/store.ts +1 -1
- package/packages/core/src/store/remote/client.ts +1 -1
- package/packages/core/src/store/remote/compatibility.ts +90 -0
- package/packages/core/src/store/remote/credentials.ts +28 -0
- package/packages/core/src/store/remote/index.ts +14 -1
- package/packages/core/src/store/remote/protocol.ts +25 -0
- package/packages/core/src/store/remote/token.ts +74 -13
- package/packages/core/src/store/ticket-asset-snapshots.ts +192 -0
- package/packages/core/src/store/ticket-assets.ts +86 -8
- package/packages/core/src/store/types.ts +1 -1
- package/packages/core/src/testing/index.ts +7 -0
- package/skills/guide/SKILL.md +232 -34
- package/skills/guide/references/operator-api.md +142 -13
- package/skills/guide/references/plugin-authoring.md +10 -2
- package/skills/guide/references/remote-store-protocol.md +199 -144
- package/skills/guide/references/remote-ticket-protocol.md +253 -18
- package/skills/guide/references/setup.md +199 -16
- package/skills/guide/references/ticket-dependencies.md +1 -1
- package/skills/guide/references/ticket-migration.md +64 -0
- package/skills/operate/SKILL.md +126 -36
- package/skills/tickets/SKILL.md +71 -0
- package/templates/hosted-service/package.json +13 -0
- package/templates/hosted-service/vercel.json +7 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { defineReducer } from './incremental'
|
|
2
|
+
import type { AbEvent } from '../events/catalog'
|
|
3
|
+
|
|
4
|
+
/** Latest completed occurrence only: never replay an obsolete publication backlog. */
|
|
5
|
+
export const summaryBoundaryReducer = defineReducer<
|
|
6
|
+
{
|
|
7
|
+
occurrence?: number
|
|
8
|
+
boundary?: number
|
|
9
|
+
sessions: Record<string, true>
|
|
10
|
+
},
|
|
11
|
+
AbEvent,
|
|
12
|
+
{ occurrence: number; boundary: number } | undefined
|
|
13
|
+
>({
|
|
14
|
+
version: 1,
|
|
15
|
+
initial: () => ({ sessions: {} }),
|
|
16
|
+
fold(acc, events) {
|
|
17
|
+
for (const event of events) {
|
|
18
|
+
if (
|
|
19
|
+
[
|
|
20
|
+
'plan.started',
|
|
21
|
+
'plan-review.started',
|
|
22
|
+
'implement.started',
|
|
23
|
+
'code-review.started',
|
|
24
|
+
'verify.started',
|
|
25
|
+
'reconcile.started',
|
|
26
|
+
'finalize.started',
|
|
27
|
+
'finalize.step-started',
|
|
28
|
+
].includes(event.type)
|
|
29
|
+
) {
|
|
30
|
+
acc.occurrence = event.seq
|
|
31
|
+
delete acc.boundary
|
|
32
|
+
}
|
|
33
|
+
if (event.type === 'session.started') acc.sessions[event.payload.session] = true
|
|
34
|
+
if (event.type === 'session.ended') delete acc.sessions[event.payload.session]
|
|
35
|
+
if (
|
|
36
|
+
acc.occurrence !== undefined &&
|
|
37
|
+
(event.type === 'session.ended' ||
|
|
38
|
+
[
|
|
39
|
+
'plan.completed',
|
|
40
|
+
'plan-review.verdict',
|
|
41
|
+
'implement.completed',
|
|
42
|
+
'code-review.verdict',
|
|
43
|
+
'verify.completed',
|
|
44
|
+
'reconcile.completed',
|
|
45
|
+
'finalize.completed',
|
|
46
|
+
'finalize.step-completed',
|
|
47
|
+
'phase.failed',
|
|
48
|
+
'escalation.raised',
|
|
49
|
+
'publication.requested',
|
|
50
|
+
].includes(event.type))
|
|
51
|
+
)
|
|
52
|
+
acc.boundary = event.seq
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
finish: (acc) =>
|
|
56
|
+
acc.occurrence !== undefined &&
|
|
57
|
+
acc.boundary !== undefined &&
|
|
58
|
+
Object.keys(acc.sessions).length === 0
|
|
59
|
+
? { occurrence: acc.occurrence, boundary: acc.boundary }
|
|
60
|
+
: undefined,
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
export function summaryBoundary(events: readonly AbEvent[]) {
|
|
64
|
+
return summaryBoundaryReducer.reduce(events)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type SummaryRequest = Extract<AbEvent, { type: 'pr-summary.requested' }>
|
|
68
|
+
export const summaryRequestsReducer = defineReducer<
|
|
69
|
+
{
|
|
70
|
+
requests: SummaryRequest[]
|
|
71
|
+
settled: number[]
|
|
72
|
+
},
|
|
73
|
+
AbEvent,
|
|
74
|
+
SummaryRequest[]
|
|
75
|
+
>({
|
|
76
|
+
version: 1,
|
|
77
|
+
initial: () => ({ requests: [], settled: [] }),
|
|
78
|
+
fold(acc, events) {
|
|
79
|
+
for (const event of events) {
|
|
80
|
+
if (event.type === 'pr-summary.requested') acc.requests.push(event)
|
|
81
|
+
if (event.type === 'pr-summary.settled') acc.settled.push(event.payload.request)
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
finish: (acc) => acc.requests.filter((event) => !acc.settled.includes(event.seq)),
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
export function pendingSummaryRequests(events: readonly AbEvent[]) {
|
|
88
|
+
return summaryRequestsReducer.reduce(events)
|
|
89
|
+
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { executionNoProgressReducer } from '../processes/execution-no-progress'
|
|
2
|
+
import { summaryBoundaryReducer, summaryRequestsReducer } from './pr-summary'
|
|
3
|
+
import { dispatchFailureBudgetReducer } from '../processes/dispatch-failure-budget'
|
|
1
4
|
import { ticketSourceHealthReducer } from './ticket-source-health'
|
|
2
5
|
/**
|
|
3
6
|
* Every incremental reducer, with its published version and the whole-array
|
|
@@ -58,6 +61,9 @@ export interface RegisteredReducer {
|
|
|
58
61
|
/** Parameterized reducers are registered with representative arguments; the
|
|
59
62
|
* accumulator shape does not depend on them. */
|
|
60
63
|
export const REDUCERS: Record<string, RegisteredReducer> = {
|
|
64
|
+
executionNoProgress: { reducer: executionNoProgressReducer, covers: [] },
|
|
65
|
+
summaryBoundary: { reducer: summaryBoundaryReducer, covers: ['summaryBoundary'] },
|
|
66
|
+
summaryRequests: { reducer: summaryRequestsReducer, covers: ['pendingSummaryRequests'] },
|
|
61
67
|
ticketSourceHealth: { reducer: ticketSourceHealthReducer, covers: ['reduceTicketSourceHealth'] },
|
|
62
68
|
build: { reducer: buildReducer, covers: ['reduceBuild'] },
|
|
63
69
|
harvest: { reducer: harvestReducer, covers: ['reduceHarvest'] },
|
|
@@ -97,6 +103,10 @@ export const REDUCERS: Record<string, RegisteredReducer> = {
|
|
|
97
103
|
covers: ['openHarvestExecutions'],
|
|
98
104
|
},
|
|
99
105
|
openTick: { reducer: openTickReducer('run-a'), covers: ['hasOpenTick'] },
|
|
106
|
+
dispatchFailureBudget: {
|
|
107
|
+
reducer: dispatchFailureBudgetReducer(false),
|
|
108
|
+
covers: ['dispatchFailureBudget'],
|
|
109
|
+
},
|
|
100
110
|
infrastructureFailureReset: {
|
|
101
111
|
reducer: infrastructureFailureResetReducer,
|
|
102
112
|
covers: ['infrastructureFailureResetSeq'],
|
|
@@ -113,6 +113,8 @@ export interface VerifyResult {
|
|
|
113
113
|
attempt: number
|
|
114
114
|
outcome: VerifyOutcome
|
|
115
115
|
report?: ArtifactRef
|
|
116
|
+
/** Optional classification on kernel policy skips; historical skips omit it. */
|
|
117
|
+
skipCause?: 'post-reconcile-policy'
|
|
116
118
|
/** Present exactly when `outcome === "skipped"`. */
|
|
117
119
|
reason?: string
|
|
118
120
|
/** seq of the `verify.completed` event — what `cycleSince` is compared to. */
|
|
@@ -457,6 +459,11 @@ function foldBuild(acc: BuildAcc, events: readonly AbEvent[]): void {
|
|
|
457
459
|
break
|
|
458
460
|
case 'workspace.provisioned':
|
|
459
461
|
case 'workspace.released':
|
|
462
|
+
case 'pr-summary.requested':
|
|
463
|
+
case 'pr-summary.attempted':
|
|
464
|
+
case 'pr-summary.result':
|
|
465
|
+
case 'pr-summary.settled':
|
|
466
|
+
case 'finalize.step-started':
|
|
460
467
|
case 'publication.requested':
|
|
461
468
|
// Inert loss fact (AUT-328): the dispatcher released a workspace with a
|
|
462
469
|
// pending publication. Recovery runs through the existing
|
|
@@ -689,6 +696,7 @@ function foldBuild(acc: BuildAcc, events: readonly AbEvent[]): void {
|
|
|
689
696
|
outcome: result.outcome,
|
|
690
697
|
report: result.report,
|
|
691
698
|
...(result.reason !== undefined ? { reason: result.reason } : {}),
|
|
699
|
+
...(result.skipCause !== undefined ? { skipCause: result.skipCause } : {}),
|
|
692
700
|
seq: event.seq,
|
|
693
701
|
})
|
|
694
702
|
if (verify.currentStep === result.step) {
|
|
@@ -108,6 +108,8 @@ export const policyEscalationCauseSchema = z.enum([
|
|
|
108
108
|
'review-round-limit',
|
|
109
109
|
'verify-failure-limit',
|
|
110
110
|
'reconcile-no-progress',
|
|
111
|
+
'dispatch-failure-limit',
|
|
112
|
+
'execution-no-progress',
|
|
111
113
|
'setup-failure-limit',
|
|
112
114
|
'infrastructure-failure-limit',
|
|
113
115
|
'phase-attempt-limit',
|
|
@@ -92,6 +92,18 @@ export const OPERATOR_TOOL_ANNOTATIONS = {
|
|
|
92
92
|
idempotentHint: false,
|
|
93
93
|
approval: 'default',
|
|
94
94
|
},
|
|
95
|
+
'tickets.states': {
|
|
96
|
+
readOnlyHint: true,
|
|
97
|
+
destructiveHint: false,
|
|
98
|
+
idempotentHint: true,
|
|
99
|
+
approval: 'never',
|
|
100
|
+
},
|
|
101
|
+
'tickets.state.create': {
|
|
102
|
+
readOnlyHint: false,
|
|
103
|
+
destructiveHint: false,
|
|
104
|
+
idempotentHint: false,
|
|
105
|
+
approval: 'default',
|
|
106
|
+
},
|
|
95
107
|
'tickets.list': {
|
|
96
108
|
readOnlyHint: true,
|
|
97
109
|
destructiveHint: false,
|
|
@@ -92,6 +92,9 @@ export type {
|
|
|
92
92
|
OperatorTicketQueue,
|
|
93
93
|
} from './tickets'
|
|
94
94
|
export {
|
|
95
|
+
prepareOperatorTicketAssets,
|
|
96
|
+
listOperatorTicketStates,
|
|
97
|
+
createOperatorTicketState,
|
|
95
98
|
getOperatorTicket,
|
|
96
99
|
listOperatorTickets,
|
|
97
100
|
mutateOperatorTicket,
|
|
@@ -125,3 +128,12 @@ export type { HarvestStatusView } from '../cli/harvest'
|
|
|
125
128
|
export type { RepositoryStatus } from '../cli/repository-status'
|
|
126
129
|
export type { BuildSummary } from '../cli/status'
|
|
127
130
|
export type { SessionRecord } from '../store/types'
|
|
131
|
+
|
|
132
|
+
export {
|
|
133
|
+
effectiveConfig,
|
|
134
|
+
selectEffectiveConfig,
|
|
135
|
+
findEffectiveConfigRef,
|
|
136
|
+
decodeEffectiveConfig,
|
|
137
|
+
} from './query'
|
|
138
|
+
|
|
139
|
+
export { ticketStateCreateRequestSchema } from './protocol'
|
|
@@ -55,15 +55,27 @@ export const ticketCreateRequestSchema = z.strictObject({
|
|
|
55
55
|
state: z.string().min(1).optional(),
|
|
56
56
|
blockedBy: z.array(z.string().min(1)).optional(),
|
|
57
57
|
})
|
|
58
|
+
const expectedRevisionSchema = z
|
|
59
|
+
.string()
|
|
60
|
+
.refine((v) => v.trim().length > 0, 'expectedRevision must be nonblank')
|
|
61
|
+
.optional()
|
|
58
62
|
export const ticketUpdateRequestSchema = z
|
|
59
63
|
.strictObject({
|
|
64
|
+
expectedRevision: expectedRevisionSchema,
|
|
60
65
|
title: z.string().optional(),
|
|
61
66
|
body: z.string().optional(),
|
|
62
67
|
labels: z.array(z.string()).optional(),
|
|
63
68
|
})
|
|
64
|
-
.refine(
|
|
65
|
-
|
|
69
|
+
.refine(
|
|
70
|
+
(value) => value.title !== undefined || value.body !== undefined || value.labels !== undefined,
|
|
71
|
+
'update must name at least one field',
|
|
72
|
+
)
|
|
73
|
+
export const ticketMoveRequestSchema = z.strictObject({
|
|
74
|
+
expectedRevision: expectedRevisionSchema,
|
|
75
|
+
state: z.string().min(1),
|
|
76
|
+
})
|
|
66
77
|
export const ticketBlockerRequestSchema = z.strictObject({
|
|
78
|
+
expectedRevision: expectedRevisionSchema,
|
|
67
79
|
blockerIds: z.array(z.string().min(1)).min(1),
|
|
68
80
|
})
|
|
69
81
|
export type OperatorTicketCreateRequest = z.infer<typeof ticketCreateRequestSchema>
|
|
@@ -96,3 +108,8 @@ export const sessionApprovalRequestSchema = z.strictObject({
|
|
|
96
108
|
decision: z.enum(['approve', 'deny']),
|
|
97
109
|
})
|
|
98
110
|
export type OperatorSessionApprovalRequest = z.infer<typeof sessionApprovalRequestSchema>
|
|
111
|
+
|
|
112
|
+
export const ticketStateCreateRequestSchema = z.strictObject({
|
|
113
|
+
name: z.string(),
|
|
114
|
+
about: z.string().optional(),
|
|
115
|
+
})
|
|
@@ -38,7 +38,7 @@ export type BuildListScope = 'active' | 'queued' | 'all'
|
|
|
38
38
|
|
|
39
39
|
export class OperatorQueryError extends Error {
|
|
40
40
|
constructor(
|
|
41
|
-
readonly code: 'not-found' | 'effective-config-unavailable',
|
|
41
|
+
readonly code: 'not-found' | 'effective-config-unavailable' | 'external-ticket-source',
|
|
42
42
|
message: string,
|
|
43
43
|
) {
|
|
44
44
|
super(message)
|
|
@@ -121,48 +121,70 @@ export async function effectiveConfig(
|
|
|
121
121
|
status: ReturnType<typeof reduceDispatchStatus>
|
|
122
122
|
}> {
|
|
123
123
|
const repositoryEvents = journal ?? (await readRepoEventsIfRecorded(store, repo))
|
|
124
|
+
const status = selectEffectiveConfig(repositoryEvents, repo)
|
|
125
|
+
const ref = status.effectiveConfig!
|
|
126
|
+
const artifact = await store.getRepoArtifact(repo, ref.kind, ref.rev)
|
|
127
|
+
if (artifact === null) {
|
|
128
|
+
throw new OperatorQueryError(
|
|
129
|
+
'effective-config-unavailable',
|
|
130
|
+
`effective config ${ref.kind}@${ref.rev} is not retrievable`,
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
config: decodeEffectiveConfig(artifact.content, `${ref.kind}@${ref.rev}`),
|
|
135
|
+
repositoryEvents,
|
|
136
|
+
status,
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function latestDispatchConfigStatus(repositoryEvents: RepositoryEvent[]) {
|
|
124
141
|
let latestRun: string | undefined
|
|
125
142
|
for (const event of repositoryEvents) {
|
|
126
143
|
if (event.type === 'dispatcher.run-started') latestRun = event.payload.run
|
|
127
144
|
}
|
|
128
|
-
|
|
145
|
+
return latestRun === undefined ? undefined : reduceDispatchStatus(repositoryEvents, latestRun)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Absence is distinct from a selected reference whose bytes cannot be read. */
|
|
149
|
+
export function findEffectiveConfigRef(repositoryEvents: RepositoryEvent[]) {
|
|
150
|
+
return latestDispatchConfigStatus(repositoryEvents)?.effectiveConfig
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function selectEffectiveConfig(repositoryEvents: RepositoryEvent[], repo: string) {
|
|
154
|
+
const status = latestDispatchConfigStatus(repositoryEvents)
|
|
155
|
+
if (status === undefined) {
|
|
129
156
|
throw new OperatorQueryError(
|
|
130
157
|
'effective-config-unavailable',
|
|
131
158
|
`effective config unavailable for repository "${repo}": no dispatcher run is recorded`,
|
|
132
159
|
)
|
|
133
160
|
}
|
|
134
|
-
|
|
135
|
-
const ref = status.effectiveConfig
|
|
136
|
-
if (ref === undefined) {
|
|
161
|
+
if (status.effectiveConfig === undefined) {
|
|
137
162
|
throw new OperatorQueryError(
|
|
138
163
|
'effective-config-unavailable',
|
|
139
164
|
`effective config unavailable for repository "${repo}"`,
|
|
140
165
|
)
|
|
141
166
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
`effective config ${ref.kind}@${ref.rev} is not retrievable`,
|
|
147
|
-
)
|
|
148
|
-
}
|
|
167
|
+
return status
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function decodeEffectiveConfig(content: Uint8Array, label: string): Config {
|
|
149
171
|
let raw: unknown
|
|
150
172
|
try {
|
|
151
|
-
raw = JSON.parse(new TextDecoder().decode(
|
|
173
|
+
raw = JSON.parse(new TextDecoder().decode(content))
|
|
152
174
|
} catch {
|
|
153
175
|
throw new OperatorQueryError(
|
|
154
176
|
'effective-config-unavailable',
|
|
155
|
-
`effective config ${
|
|
177
|
+
`effective config ${label} is not valid JSON`,
|
|
156
178
|
)
|
|
157
179
|
}
|
|
158
180
|
const parsed = configSchema.safeParse(raw)
|
|
159
181
|
if (!parsed.success) {
|
|
160
182
|
throw new OperatorQueryError(
|
|
161
183
|
'effective-config-unavailable',
|
|
162
|
-
`effective config ${
|
|
184
|
+
`effective config ${label} is invalid: ${parsed.error.message}`,
|
|
163
185
|
)
|
|
164
186
|
}
|
|
165
|
-
return
|
|
187
|
+
return parsed.data
|
|
166
188
|
}
|
|
167
189
|
|
|
168
190
|
export interface OperatorBuildView {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TicketRevisionConflict, TicketRevisionUnsupported } from '../ports/tickets/revision'
|
|
1
2
|
/**
|
|
2
3
|
* The agent tool registry — one typed table that is the single source of
|
|
3
4
|
* truth for the agent-facing operator surface (AUT-338).
|
|
@@ -58,6 +59,9 @@ import { answerAction, controlPrechecks, requireRouteBuild, RouteRefusalError }
|
|
|
58
59
|
import { SandboxOperationError } from '../ports/workspace/operator-sandbox'
|
|
59
60
|
import type { OperatorSandboxService } from './sandbox'
|
|
60
61
|
import {
|
|
62
|
+
prepareOperatorTicketAssets,
|
|
63
|
+
listOperatorTicketStates,
|
|
64
|
+
createOperatorTicketState,
|
|
61
65
|
getOperatorTicket,
|
|
62
66
|
listOperatorTickets,
|
|
63
67
|
mutateOperatorTicket,
|
|
@@ -598,7 +602,13 @@ export const TOOLS: readonly ToolEntry[] = [
|
|
|
598
602
|
user: attributed(ctx),
|
|
599
603
|
...(ctx.via !== undefined ? { via: ctx.via } : {}),
|
|
600
604
|
action,
|
|
601
|
-
...(readTicketBody !== undefined
|
|
605
|
+
...(readTicketBody !== undefined
|
|
606
|
+
? {
|
|
607
|
+
readTicketBody,
|
|
608
|
+
prepareTicketAssets: (ref, store, repo) =>
|
|
609
|
+
prepareOperatorTicketAssets({ ref, store, repo, backend: ctx.tickets }),
|
|
610
|
+
}
|
|
611
|
+
: {}),
|
|
602
612
|
})
|
|
603
613
|
},
|
|
604
614
|
),
|
|
@@ -691,6 +701,30 @@ export const TOOLS: readonly ToolEntry[] = [
|
|
|
691
701
|
})
|
|
692
702
|
},
|
|
693
703
|
),
|
|
704
|
+
defineTool(
|
|
705
|
+
'tickets.states',
|
|
706
|
+
'List all ticket states with ticket counts and optional purposes.',
|
|
707
|
+
z.strictObject({ repo: z.string().min(1) }),
|
|
708
|
+
'Array of {name,tickets,about?}.',
|
|
709
|
+
async (raw, ctx) => {
|
|
710
|
+
const input = raw as { repo: string }
|
|
711
|
+
return listOperatorTicketStates({
|
|
712
|
+
store: ctx.store,
|
|
713
|
+
repo: input.repo,
|
|
714
|
+
backend: ticketsOf(ctx),
|
|
715
|
+
})
|
|
716
|
+
},
|
|
717
|
+
),
|
|
718
|
+
defineTool(
|
|
719
|
+
'tickets.state.create',
|
|
720
|
+
'Create a custom ticket state with an optional one-line purpose. Hosted duplicates are refused.',
|
|
721
|
+
z.strictObject({ repo: z.string().min(1), name: z.string(), about: z.string().optional() }),
|
|
722
|
+
'{state: {name,tickets,about?},created}.',
|
|
723
|
+
async (raw, ctx) => {
|
|
724
|
+
const input = raw as { repo: string; name: string; about?: string }
|
|
725
|
+
return createOperatorTicketState({ store: ctx.store, backend: ticketsOf(ctx), ...input })
|
|
726
|
+
},
|
|
727
|
+
),
|
|
694
728
|
defineTool(
|
|
695
729
|
'tickets.list',
|
|
696
730
|
'List the repository’s ticket grooming queue from the configured ticket source.',
|
|
@@ -709,9 +743,9 @@ export const TOOLS: readonly ToolEntry[] = [
|
|
|
709
743
|
),
|
|
710
744
|
defineTool(
|
|
711
745
|
'tickets.get',
|
|
712
|
-
'Show one ticket with its blockers and any related build.',
|
|
746
|
+
'Show one ticket with its hosted comments, blockers and any related build.',
|
|
713
747
|
ticketDetailInput,
|
|
714
|
-
'{ticket, blockers, build}: the ticket, its dependency states, and the most relevant build for it (null when none).',
|
|
748
|
+
'{ticket, blockers, build}: the ticket (including hosted ticket.comments when available), its dependency states, and the most relevant build for it (null when none).',
|
|
715
749
|
async (raw, ctx) => {
|
|
716
750
|
const input = raw as z.infer<typeof ticketDetailInput>
|
|
717
751
|
return getOperatorTicket({
|
|
@@ -752,12 +786,12 @@ export const TOOLS: readonly ToolEntry[] = [
|
|
|
752
786
|
'{ticket, blockers, build}: the updated ticket.',
|
|
753
787
|
async (raw, ctx) => {
|
|
754
788
|
const input = raw as z.infer<typeof ticketsUpdateInput>
|
|
755
|
-
const { repo, id, ...patch } = input
|
|
789
|
+
const { repo, id, expectedRevision, ...patch } = input
|
|
756
790
|
return mutateOperatorTicket({
|
|
757
791
|
store: ctx.store,
|
|
758
792
|
repo,
|
|
759
793
|
backend: ticketsOf(ctx),
|
|
760
|
-
operation: { kind: 'update', id, patch },
|
|
794
|
+
operation: { kind: 'update', id, patch, expectedRevision },
|
|
761
795
|
})
|
|
762
796
|
},
|
|
763
797
|
),
|
|
@@ -772,7 +806,12 @@ export const TOOLS: readonly ToolEntry[] = [
|
|
|
772
806
|
store: ctx.store,
|
|
773
807
|
repo: input.repo,
|
|
774
808
|
backend: ticketsOf(ctx),
|
|
775
|
-
operation: {
|
|
809
|
+
operation: {
|
|
810
|
+
kind: 'block',
|
|
811
|
+
id: input.id,
|
|
812
|
+
blockerIds: input.blockerIds,
|
|
813
|
+
expectedRevision: input.expectedRevision,
|
|
814
|
+
},
|
|
776
815
|
})
|
|
777
816
|
},
|
|
778
817
|
),
|
|
@@ -787,7 +826,12 @@ export const TOOLS: readonly ToolEntry[] = [
|
|
|
787
826
|
store: ctx.store,
|
|
788
827
|
repo: input.repo,
|
|
789
828
|
backend: ticketsOf(ctx),
|
|
790
|
-
operation: {
|
|
829
|
+
operation: {
|
|
830
|
+
kind: 'unblock',
|
|
831
|
+
id: input.id,
|
|
832
|
+
blockerIds: input.blockerIds,
|
|
833
|
+
expectedRevision: input.expectedRevision,
|
|
834
|
+
},
|
|
791
835
|
})
|
|
792
836
|
},
|
|
793
837
|
),
|
|
@@ -802,7 +846,12 @@ export const TOOLS: readonly ToolEntry[] = [
|
|
|
802
846
|
store: ctx.store,
|
|
803
847
|
repo: input.repo,
|
|
804
848
|
backend: ticketsOf(ctx),
|
|
805
|
-
operation: {
|
|
849
|
+
operation: {
|
|
850
|
+
kind: 'move',
|
|
851
|
+
id: input.id,
|
|
852
|
+
state: input.state,
|
|
853
|
+
expectedRevision: input.expectedRevision,
|
|
854
|
+
},
|
|
806
855
|
})
|
|
807
856
|
},
|
|
808
857
|
),
|
|
@@ -964,6 +1013,10 @@ function mapDomainError(error: unknown): RegistryError {
|
|
|
964
1013
|
error: error.refusal.error,
|
|
965
1014
|
})
|
|
966
1015
|
}
|
|
1016
|
+
if (error instanceof TicketRevisionConflict)
|
|
1017
|
+
return new RegistryError('domain', error.message, { kind: 'conflict', error: error.message })
|
|
1018
|
+
if (error instanceof TicketRevisionUnsupported)
|
|
1019
|
+
return new RegistryError('domain', error.message, { kind: 'refusal', error: error.message })
|
|
967
1020
|
if (error instanceof TicketOperationError) {
|
|
968
1021
|
return new RegistryError('domain', error.message, {
|
|
969
1022
|
kind: error.code === 'not-found' ? 'not-found' : 'refusal',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ticketLifecycle } from '../ports/tickets/lifecycle'
|
|
1
2
|
/**
|
|
2
3
|
* A dispatcher ticket source as the registry's ticket backend (AUT-342):
|
|
3
4
|
* the tick's turn runner takes an `OperatorToolRegistry`, and
|
|
@@ -14,33 +15,29 @@ import type { Config } from '../config/schema'
|
|
|
14
15
|
import type { OperatorTicketBackend, OperatorTicketContext } from './tickets'
|
|
15
16
|
import type { TicketSource } from '../ports/types'
|
|
16
17
|
|
|
17
|
-
/** The dispatcher's resolved triage state (the same default
|
|
18
|
-
* `processes/dispatcher.ts` applies); inlined rather than imported so this
|
|
19
|
-
* operator-seam module does not pull the dispatcher's module graph. */
|
|
20
|
-
function defaultTriageState(config: Config): string {
|
|
21
|
-
return (
|
|
22
|
-
config.tickets.triageState ??
|
|
23
|
-
(config.tickets.source === 'linear' || config.tickets.source === 'hosted'
|
|
24
|
-
? 'Backlog'
|
|
25
|
-
: 'Triage')
|
|
26
|
-
)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
18
|
export function ticketBackendFromSource(options: {
|
|
30
19
|
source: TicketSource
|
|
31
20
|
config: Config
|
|
32
21
|
}): OperatorTicketBackend {
|
|
33
22
|
return {
|
|
34
23
|
sourceFor: (_context: OperatorTicketContext) => options.source,
|
|
35
|
-
statesFor: (_context: OperatorTicketContext,
|
|
24
|
+
statesFor: async (_context: OperatorTicketContext, source: TicketSource): Promise<string[]> => {
|
|
25
|
+
if (source.listStates) return (await source.listStates()).map((s) => s.name)
|
|
36
26
|
const tickets = options.config.tickets
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
tickets.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
27
|
+
const lifecycle = ticketLifecycle(tickets)
|
|
28
|
+
const states =
|
|
29
|
+
tickets.source === 'hosted'
|
|
30
|
+
? Object.values(lifecycle)
|
|
31
|
+
: [
|
|
32
|
+
lifecycle.triage,
|
|
33
|
+
tickets.readyState,
|
|
34
|
+
tickets.createState,
|
|
35
|
+
tickets.claimedState,
|
|
36
|
+
tickets.doneState,
|
|
37
|
+
lifecycle.proposal,
|
|
38
|
+
].filter(
|
|
39
|
+
(state): state is string => typeof state === 'string' && state.trim().length > 0,
|
|
40
|
+
)
|
|
44
41
|
return [...new Set(states)]
|
|
45
42
|
},
|
|
46
43
|
}
|