@cat-factory/orchestration 0.136.0 → 0.137.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/dist/container/engine-collaborators.d.ts +49 -0
- package/dist/container/engine-collaborators.d.ts.map +1 -0
- package/dist/container/engine-collaborators.js +93 -0
- package/dist/container/engine-collaborators.js.map +1 -0
- package/dist/container/engine-dependent-modules.d.ts +30 -0
- package/dist/container/engine-dependent-modules.d.ts.map +1 -0
- package/dist/container/engine-dependent-modules.js +54 -0
- package/dist/container/engine-dependent-modules.js.map +1 -0
- package/dist/container.d.ts +21 -3
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +31 -102
- package/dist/container.js.map +1 -1
- package/dist/modules/execution/ExecutionService.d.ts +27 -370
- package/dist/modules/execution/ExecutionService.d.ts.map +1 -1
- package/dist/modules/execution/ExecutionService.js +51 -139
- package/dist/modules/execution/ExecutionService.js.map +1 -1
- package/dist/modules/execution/ExecutionServiceDependencies.d.ts +381 -0
- package/dist/modules/execution/ExecutionServiceDependencies.d.ts.map +1 -0
- package/dist/modules/execution/ExecutionServiceDependencies.js +2 -0
- package/dist/modules/execution/ExecutionServiceDependencies.js.map +1 -0
- package/dist/modules/execution/PrVerificationReportController.d.ts +106 -0
- package/dist/modules/execution/PrVerificationReportController.d.ts.map +1 -0
- package/dist/modules/execution/PrVerificationReportController.js +134 -0
- package/dist/modules/execution/PrVerificationReportController.js.map +1 -0
- package/dist/modules/execution/RunDispatcher.d.ts +4 -0
- package/dist/modules/execution/RunDispatcher.d.ts.map +1 -1
- package/dist/modules/execution/RunDispatcher.js +13 -0
- package/dist/modules/execution/RunDispatcher.js.map +1 -1
- package/dist/modules/execution/gate-window-controllers.d.ts +209 -0
- package/dist/modules/execution/gate-window-controllers.d.ts.map +1 -0
- package/dist/modules/execution/gate-window-controllers.js +212 -0
- package/dist/modules/execution/gate-window-controllers.js.map +1 -0
- package/dist/modules/execution/prReport.logic.d.ts +25 -0
- package/dist/modules/execution/prReport.logic.d.ts.map +1 -0
- package/dist/modules/execution/prReport.logic.js +436 -0
- package/dist/modules/execution/prReport.logic.js.map +1 -0
- package/dist/modules/execution/prReportText.logic.d.ts +46 -0
- package/dist/modules/execution/prReportText.logic.d.ts.map +1 -0
- package/dist/modules/execution/prReportText.logic.js +188 -0
- package/dist/modules/execution/prReportText.logic.js.map +1 -0
- package/dist/modules/settings/WorkspaceSettingsService.d.ts.map +1 -1
- package/dist/modules/settings/WorkspaceSettingsService.js +1 -0
- package/dist/modules/settings/WorkspaceSettingsService.js.map +1 -1
- package/package.json +11 -11
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import type { AccountRepository, AgentExecutor, BlockRepository, BlueprintService, BranchUpdater, BrainstormSessionRepository, BrainstormStage, ClarityReviewRepository, Clock, DocInterviewRepository, DocumentRepository, ExecutionEventPublisher, ExecutionRepository, GateRegistry, GroupCacheHandle, IdGenerator, InitiativePresetRegistry, InitiativeRepository, IssueWritebackProvider, ModelRef, PipelineRepository, PrVerificationReportPublisher, ProviderCapabilities, ProviderRegistry, PullRequestMerger, RequirementReviewRepository, ResolveBinaryArtifactStore, ResolveRunRepoContext, RiskPolicyCacheValue, RiskPolicyRepository, RunInitiatorScope, StepResolverRegistry, SubscriptionActivationRepository, TaskRepository, TestSecretRef, TicketTrackerProvider, WorkRunner, WorkspaceRepository, WorkspaceSettingsRepository } from '@cat-factory/kernel';
|
|
2
|
+
import type { AgentKindRegistry } from '@cat-factory/agents';
|
|
3
|
+
import type { BugIntakeService, EnvironmentProvisioningService, EnvironmentTeardownService } from '@cat-factory/integrations';
|
|
4
|
+
import type { SpendService } from '@cat-factory/spend';
|
|
5
|
+
import type { BoardService } from '../board/BoardService.js';
|
|
6
|
+
import type { BrainstormService } from '../brainstorm/BrainstormService.js';
|
|
7
|
+
import type { ClarityReviewService } from '../clarity/ClarityReviewService.js';
|
|
8
|
+
import type { DocInterviewService } from '../docInterview/DocInterviewService.js';
|
|
9
|
+
import type { InitiativeInterviewService } from '../initiative/InitiativeInterviewService.js';
|
|
10
|
+
import type { InitiativeRunHarvest } from '../initiative/initiative.logic.js';
|
|
11
|
+
import type { InitiativeService } from '../initiative/InitiativeService.js';
|
|
12
|
+
import type { LlmObservabilityService } from '../observability/LlmObservabilityService.js';
|
|
13
|
+
import type { NotificationService } from '../notifications/NotificationService.js';
|
|
14
|
+
import type { RequirementReviewService } from '../requirements/RequirementReviewService.js';
|
|
15
|
+
import type { WorkspaceSettingsService } from '../settings/WorkspaceSettingsService.js';
|
|
16
|
+
import type { DocumentUrlResolver, FragmentBodyResolver, SkillResolver } from './AgentContextBuilder.js';
|
|
17
|
+
import type { ForkChatService } from './ForkChatService.js';
|
|
18
|
+
import type { PrReportLogger } from './PrVerificationReportController.js';
|
|
19
|
+
import type { KaizenScheduler } from './RunStateMachine.js';
|
|
20
|
+
import type { TesterQualityReviewer } from './TesterQualityReviewService.js';
|
|
21
|
+
/** Reconciles a Blueprinter step's tree onto the board in place (BoardScanService). */
|
|
22
|
+
export interface BlueprintReconciler {
|
|
23
|
+
reconcileBlueprint(workspaceId: string, frameId: string | null, service: BlueprintService): Promise<unknown>;
|
|
24
|
+
}
|
|
25
|
+
export interface ExecutionServiceDependencies {
|
|
26
|
+
workspaceRepository: WorkspaceRepository;
|
|
27
|
+
blockRepository: BlockRepository;
|
|
28
|
+
pipelineRepository: PipelineRepository;
|
|
29
|
+
executionRepository: ExecutionRepository;
|
|
30
|
+
/**
|
|
31
|
+
* Resolves the owning account of a workspace so a service that pins no cloud
|
|
32
|
+
* provider falls back to the account's `defaultCloudProvider` at dispatch.
|
|
33
|
+
*/
|
|
34
|
+
accountRepository: AccountRepository;
|
|
35
|
+
idGenerator: IdGenerator;
|
|
36
|
+
clock: Clock;
|
|
37
|
+
agentExecutor: AgentExecutor;
|
|
38
|
+
/**
|
|
39
|
+
* The app-owned agent-kind registry, threaded through to the trait/inline-surface checks
|
|
40
|
+
* and a registered kind's pre/post-op hooks. `createCore` defaults it to
|
|
41
|
+
* `defaultAgentKindRegistry()` when a facade doesn't inject the shared instance.
|
|
42
|
+
*/
|
|
43
|
+
agentKindRegistry: AgentKindRegistry;
|
|
44
|
+
/**
|
|
45
|
+
* The app-owned polling-gate registry (the built-in `@cat-factory/gates` suite installed by
|
|
46
|
+
* the facade + any deployment-registered gates), threaded to the dispatcher's gate machine.
|
|
47
|
+
* `createCore` defaults it to `defaultGateRegistry()` (empty) when a facade doesn't inject one.
|
|
48
|
+
*/
|
|
49
|
+
gateRegistry: GateRegistry;
|
|
50
|
+
/**
|
|
51
|
+
* The app-owned step-completion-resolver registry (deployment-registered resolvers),
|
|
52
|
+
* threaded to the dispatcher. `createCore` defaults it to `defaultStepResolverRegistry()`.
|
|
53
|
+
*/
|
|
54
|
+
stepResolverRegistry: StepResolverRegistry;
|
|
55
|
+
/**
|
|
56
|
+
* The app-owned provider registry (gate data sources keyed by {@link ProviderToken}), threaded
|
|
57
|
+
* to the dispatcher's gate machine so its {@link GateContext} reads the wired providers.
|
|
58
|
+
* `createCore` defaults it to `defaultProviderRegistry()` (empty ⇒ every gate passes through).
|
|
59
|
+
*/
|
|
60
|
+
providerRegistry: ProviderRegistry;
|
|
61
|
+
/**
|
|
62
|
+
* The app-owned initiative-preset registry, threaded into the context builder so a spawned /
|
|
63
|
+
* planning run resolves its preset steering. `createCore` defaults it to
|
|
64
|
+
* `defaultInitiativePresetRegistry()` when a facade doesn't inject the shared instance.
|
|
65
|
+
*/
|
|
66
|
+
initiativePresetRegistry: InitiativePresetRegistry;
|
|
67
|
+
workRunner: WorkRunner;
|
|
68
|
+
executionEventPublisher: ExecutionEventPublisher;
|
|
69
|
+
boardService: BoardService;
|
|
70
|
+
spendService: SpendService;
|
|
71
|
+
/**
|
|
72
|
+
* Optional: when the document-source integration is configured, documents
|
|
73
|
+
* linked to a block are resolved here and fed to the agent as extra context.
|
|
74
|
+
*/
|
|
75
|
+
documentRepository?: DocumentRepository;
|
|
76
|
+
/**
|
|
77
|
+
* Optional: canonicalises a URL named in a block's description to the document's stable
|
|
78
|
+
* `(source, externalId)` (via the document providers' `parseRef`) so a pasted design/doc
|
|
79
|
+
* link auto-matches its imported page even when the URL carries title/tracking noise.
|
|
80
|
+
* Forwarded to {@link AgentContextBuilder}; absent → url-string matching only.
|
|
81
|
+
*/
|
|
82
|
+
documentUrlResolver?: DocumentUrlResolver;
|
|
83
|
+
/**
|
|
84
|
+
* Optional: when the task-source integration is configured, tracker issues
|
|
85
|
+
* linked to a block are resolved here and fed to the agent as extra context.
|
|
86
|
+
*/
|
|
87
|
+
taskRepository?: TaskRepository;
|
|
88
|
+
/**
|
|
89
|
+
* Optional: when the requirements-review feature is configured, a block's
|
|
90
|
+
* reworked ("incorporated") requirements are read here. When present they REPLACE
|
|
91
|
+
* the block's description + linked docs/tasks as the agent context (for every
|
|
92
|
+
* step) and become the per-task input the spec-writer aggregates. Absent
|
|
93
|
+
* → the engine uses the original description + docs/tasks unchanged.
|
|
94
|
+
*/
|
|
95
|
+
requirementReviewRepository?: RequirementReviewRepository;
|
|
96
|
+
/**
|
|
97
|
+
* Optional: when the interactive document-interview feature is configured (WS5), a block's
|
|
98
|
+
* synthesized authoring brief is read here and folded into the doc-writer's context. Absent
|
|
99
|
+
* → the writer runs off the raw outline/description unchanged.
|
|
100
|
+
*/
|
|
101
|
+
docInterviewRepository?: DocInterviewRepository;
|
|
102
|
+
/**
|
|
103
|
+
* Optional: the requirements-review feature's service, present when the reviewer is
|
|
104
|
+
* wired. Drives the special `requirements-review` gate step (run reviewer inline, the
|
|
105
|
+
* iterative answer → incorporate → re-review loop). Absent → the gate step passes
|
|
106
|
+
* through so pipelines run unchanged without the feature.
|
|
107
|
+
*/
|
|
108
|
+
requirementReviewService?: RequirementReviewService;
|
|
109
|
+
/**
|
|
110
|
+
* Optional: the interactive document-interview service (WS5). When wired, the
|
|
111
|
+
* `doc-interviewer` step converses with the human (park/answer/resume) to refine a
|
|
112
|
+
* document's scope/structure and synthesizes an authoring brief the writer starts from.
|
|
113
|
+
* Absent (or no model) → the interviewer step passes through so document pipelines run
|
|
114
|
+
* unchanged off the raw outline.
|
|
115
|
+
*/
|
|
116
|
+
docInterviewService?: DocInterviewService;
|
|
117
|
+
/**
|
|
118
|
+
* Optional: the inline grounded-chat responder for the implementation-fork decision phase.
|
|
119
|
+
* When wired, a human chat turn about the surfaced forks is answered by an inline LLM in the
|
|
120
|
+
* durable driver; absent (no model) the chat degrades to a canned "chat unavailable" reply so
|
|
121
|
+
* pick / custom still work. Passed to the {@link ForkDecisionController}.
|
|
122
|
+
*/
|
|
123
|
+
forkChatService?: ForkChatService;
|
|
124
|
+
/**
|
|
125
|
+
* Optional: the inline reviewer for the test quality-control companion. When wired (and a
|
|
126
|
+
* Tester step has the companion enabled), each Tester report is audited for coverage before
|
|
127
|
+
* the greenlight/fixer decision and an inadequate report loops the Tester. Passed straight
|
|
128
|
+
* to the {@link TesterController}. Absent → QC is a pass-through.
|
|
129
|
+
*/
|
|
130
|
+
testerQualityReviewer?: TesterQualityReviewer;
|
|
131
|
+
/**
|
|
132
|
+
* Optional: the Kaizen agent's scheduler. When wired, a run reaching a terminal state
|
|
133
|
+
* schedules a post-run grading for each completed agent step (skipping verified combos).
|
|
134
|
+
* Structural so the engine doesn't depend on the concrete service. Absent → no grading.
|
|
135
|
+
*/
|
|
136
|
+
kaizenScheduler?: KaizenScheduler;
|
|
137
|
+
/**
|
|
138
|
+
* Optional: persistence for the clarity-review (bug-report triage) feature. Read here
|
|
139
|
+
* to substitute a converged clarified report as the downstream agent context (the
|
|
140
|
+
* mirror of `requirementReviewRepository`). Absent → no substitution.
|
|
141
|
+
*/
|
|
142
|
+
clarityReviewRepository?: ClarityReviewRepository;
|
|
143
|
+
/**
|
|
144
|
+
* Optional: the clarity-review feature's service, present when the reviewer is wired.
|
|
145
|
+
* Drives the special `clarity-review` gate step (inline reviewer + the iterative
|
|
146
|
+
* answer → incorporate → re-review loop). Absent → the gate step passes through.
|
|
147
|
+
*/
|
|
148
|
+
clarityReviewService?: ClarityReviewService;
|
|
149
|
+
/**
|
|
150
|
+
* Optional: the brainstorm (structured-dialogue) feature's services, one per stage, present
|
|
151
|
+
* when the brainstorm module is wired. Drive the special `requirements-brainstorm` /
|
|
152
|
+
* `architecture-brainstorm` gate steps (inline option-generator + the iterative propose →
|
|
153
|
+
* pick → incorporate → re-run loop). Absent → the gate steps pass through.
|
|
154
|
+
*/
|
|
155
|
+
brainstormServices?: Record<BrainstormStage, BrainstormService>;
|
|
156
|
+
/**
|
|
157
|
+
* Optional: persistence for the brainstorm feature. Read by the agent-context builder to
|
|
158
|
+
* surface a converged `architecture-brainstorm` direction to the architect (the mirror of
|
|
159
|
+
* `requirementReviewRepository`). Absent → no substitution.
|
|
160
|
+
*/
|
|
161
|
+
brainstormSessionRepository?: BrainstormSessionRepository;
|
|
162
|
+
/**
|
|
163
|
+
* Optional: resolves fragment ids against the merged tenant catalog (managed +
|
|
164
|
+
* document-backed fragments), live-resolving linked Confluence/Notion/GitHub
|
|
165
|
+
* documents at run time. Wired only when the prompt-fragment library is
|
|
166
|
+
* configured; absent → the engine resolves against the static built-in pool.
|
|
167
|
+
*/
|
|
168
|
+
fragmentResolver?: FragmentBodyResolver;
|
|
169
|
+
/**
|
|
170
|
+
* Optional: resolves a `skill` step's picked skill to its instructions + resource bodies for
|
|
171
|
+
* the run (see {@link SkillResolver}). Wired only when the repo-sourced Claude Skills library is
|
|
172
|
+
* configured; a skill step dispatched with this unwired fails loudly rather than running blank.
|
|
173
|
+
*/
|
|
174
|
+
skillResolver?: SkillResolver;
|
|
175
|
+
/**
|
|
176
|
+
* Optional: when the individual-usage subscription store is configured, a finished
|
|
177
|
+
* run's per-run credential activation is deleted here the moment it reaches a terminal
|
|
178
|
+
* state, bounding standing exposure to the run's own lifetime (the TTL sweep is the
|
|
179
|
+
* backstop). Absent → activations are reclaimed by the TTL sweep alone.
|
|
180
|
+
*/
|
|
181
|
+
subscriptionActivationRepository?: SubscriptionActivationRepository;
|
|
182
|
+
/**
|
|
183
|
+
* Optional: resolve a workspace's per-agent-kind default model id (the same resolver
|
|
184
|
+
* the container executor uses for dispatch). The personal-credential gate consults it
|
|
185
|
+
* so a run whose block has NO pinned model but whose workspace default resolves to an
|
|
186
|
+
* individual-usage vendor is still gated up-front — matching what dispatch will resolve,
|
|
187
|
+
* instead of starting and then failing on a missing activation. Absent → the gate sees
|
|
188
|
+
* only the block's pinned model (env-routing defaults are operator-level and not gated).
|
|
189
|
+
*/
|
|
190
|
+
resolveWorkspaceModelDefault?: (workspaceId: string, agentKind: string, modelPresetId?: string) => Promise<string | undefined>;
|
|
191
|
+
/**
|
|
192
|
+
* Optional: resolve the provider capabilities (configured direct keys +
|
|
193
|
+
* subscription vendors + whether Cloudflare AI is enabled) for a workspace and the
|
|
194
|
+
* run initiator. The start guard uses it to block a pipeline whose steps' canonical
|
|
195
|
+
* models have no usable provider. Absent → the guard is skipped (tests / unconfigured
|
|
196
|
+
* facades), exactly like the existing optional engine deps.
|
|
197
|
+
*/
|
|
198
|
+
resolveProviderCapabilities?: (workspaceId: string, initiatedBy?: string | null) => Promise<ProviderCapabilities>;
|
|
199
|
+
/**
|
|
200
|
+
* Optional: whether a container-only subscription harness ref (`claude-code` / `codex`)
|
|
201
|
+
* can run as an INLINE LLM call in this deployment (local mode's ambient CLI). The preset
|
|
202
|
+
* satisfiability guard uses it so an inline step pinned to a subscription model is
|
|
203
|
+
* satisfiable where the harness runs inline, and refused where it doesn't (Node/Worker).
|
|
204
|
+
* From `config.agents.inlineHarnessRef`; absent → no inline harness support.
|
|
205
|
+
*/
|
|
206
|
+
inlineHarnessRef?: (ref: ModelRef) => boolean;
|
|
207
|
+
/**
|
|
208
|
+
* Optional: when the environment integration is configured, a `deployer` step
|
|
209
|
+
* provisions an ephemeral environment deterministically through this service
|
|
210
|
+
* (no LLM), and downstream steps discover the resulting env via it.
|
|
211
|
+
*/
|
|
212
|
+
environmentProvisioning?: EnvironmentProvisioningService;
|
|
213
|
+
/**
|
|
214
|
+
* Optional: resolve the NON-secret refs (key + description) of the sensitive test credentials
|
|
215
|
+
* for a run block's service frame, folded into the tester prompt by the context builder.
|
|
216
|
+
* Wired from the facade's `TestSecretsService`; absent ⇒ no advertised secrets. NEVER values.
|
|
217
|
+
*/
|
|
218
|
+
resolveTestSecretRefs?: (workspaceId: string, blockId: string) => Promise<TestSecretRef[]>;
|
|
219
|
+
/**
|
|
220
|
+
* Optional: resolves the binary-artifact store (UI screenshots + reference design images)
|
|
221
|
+
* for a workspace's account; the `visual-confirmation` gate reads it. Absent (or resolving
|
|
222
|
+
* to null — storage not configured) → the gate passes through (auto-advances), since there
|
|
223
|
+
* is nowhere to read screenshots from.
|
|
224
|
+
*/
|
|
225
|
+
resolveBinaryArtifactStore?: ResolveBinaryArtifactStore;
|
|
226
|
+
/**
|
|
227
|
+
* Optional: tears down ephemeral environments. Wired alongside
|
|
228
|
+
* {@link environmentProvisioning}; the `human-test` gate uses it to destroy an env on
|
|
229
|
+
* confirm / recreate / on-demand. Absent → the gate's destroy/recreate is a no-op.
|
|
230
|
+
*/
|
|
231
|
+
environmentTeardown?: EnvironmentTeardownService;
|
|
232
|
+
/**
|
|
233
|
+
* Optional: merges the repo default branch into a block's PR branch server-side. Wired
|
|
234
|
+
* when GitHub is configured; the `human-test` gate's "pull latest main" action uses it
|
|
235
|
+
* (a clean merge rebuilds the env; a conflict escalates to the conflict-resolver). Absent
|
|
236
|
+
* → pulling main is unavailable on the gate.
|
|
237
|
+
*/
|
|
238
|
+
branchUpdater?: BranchUpdater;
|
|
239
|
+
/**
|
|
240
|
+
* Optional: when the board-scan module is configured, a `blueprints` step's
|
|
241
|
+
* decomposition tree is reconciled onto the board through this (BoardScanService).
|
|
242
|
+
* Absent → a blueprint step still runs and commits its in-repo files, but the
|
|
243
|
+
* board isn't auto-updated from it.
|
|
244
|
+
*/
|
|
245
|
+
blueprintReconciler?: BlueprintReconciler;
|
|
246
|
+
/**
|
|
247
|
+
* Optional: when the initiatives module is wired, the `initiative-planner` step's
|
|
248
|
+
* plan draft is ingested into the block's initiative entity through this, and the
|
|
249
|
+
* `initiative-committer` step flips it to `executing` + mirrors the in-repo
|
|
250
|
+
* tracker. Absent → the initiative steps fail loudly (an initiative pipeline is
|
|
251
|
+
* meaningless without the module) while every other pipeline runs unchanged.
|
|
252
|
+
*/
|
|
253
|
+
initiativeService?: InitiativeService;
|
|
254
|
+
/**
|
|
255
|
+
* Optional: the initiative store, wired into the agent-context builder so an
|
|
256
|
+
* `initiative`-level run carries the interview + analysis context into the analyst/planner
|
|
257
|
+
* prompts. Same repo the {@link initiativeService} wraps; absent → those steps run off the
|
|
258
|
+
* raw block description.
|
|
259
|
+
*/
|
|
260
|
+
initiativeRepository?: InitiativeRepository;
|
|
261
|
+
/**
|
|
262
|
+
* Optional: the inline interviewer for the interactive-planning gate (slice 2). When
|
|
263
|
+
* wired, the `initiative-interviewer` step interviews the human (park/answer/resume) and
|
|
264
|
+
* synthesizes the goal/constraints brief onto the entity before the analyst/planner run.
|
|
265
|
+
* Absent (or no model) → the interviewer step passes through and planning runs off the
|
|
266
|
+
* raw block description. Requires {@link initiativeService} to persist the interview state.
|
|
267
|
+
*/
|
|
268
|
+
initiativeInterviewService?: InitiativeInterviewService;
|
|
269
|
+
/**
|
|
270
|
+
* Best-effort poke of the initiative execution loop (slice 3): called after a spawned task's
|
|
271
|
+
* PR merges (`finalizeMerge`), so its owning initiative reconciles + advances immediately
|
|
272
|
+
* rather than on the next cron sweep. Threaded through to the {@link RunStateMachine} for the
|
|
273
|
+
* symmetric terminal-run poke. Fire-and-forget; a no-op when initiatives are unwired. The
|
|
274
|
+
* optional `harvest` (slice 4) carries the settling run's follow-ups + failure cause.
|
|
275
|
+
*/
|
|
276
|
+
pokeInitiativeLoop?: (workspaceId: string, initiativeBlockId: string, harvest?: InitiativeRunHarvest) => void;
|
|
277
|
+
/**
|
|
278
|
+
* Optional: raises human-actionable notifications (a PR needs a merge decision,
|
|
279
|
+
* a no-merger pipeline finished, CI fixing gave up). Absent → those events still
|
|
280
|
+
* transition the block but no notification surfaces (tests).
|
|
281
|
+
*/
|
|
282
|
+
notificationService?: NotificationService;
|
|
283
|
+
/**
|
|
284
|
+
* Optional: resolves a workspace's runtime settings so {@link ExecutionService.start}
|
|
285
|
+
* can enforce the per-service running-task limit. Absent → the limit is never enforced
|
|
286
|
+
* (tests / unconfigured facades start runs unbounded).
|
|
287
|
+
*/
|
|
288
|
+
workspaceSettingsService?: WorkspaceSettingsService;
|
|
289
|
+
/**
|
|
290
|
+
* Optional: performs the real GitHub merge when a task should become `done`.
|
|
291
|
+
* Absent → `done` is a board-only flip (tests); when wired, `done` provably
|
|
292
|
+
* means the PR was merged on the remote.
|
|
293
|
+
*/
|
|
294
|
+
pullRequestMerger?: PullRequestMerger;
|
|
295
|
+
/**
|
|
296
|
+
* Optional: resolves a task's merge threshold preset (auto-merge ceilings + the
|
|
297
|
+
* CI-fixer attempt budget). Absent → the built-in {@link DEFAULT_RISK_POLICY}.
|
|
298
|
+
*/
|
|
299
|
+
riskPolicyRepository?: RiskPolicyRepository;
|
|
300
|
+
/**
|
|
301
|
+
* Optional: the {@link AppCaches.riskPolicy} slice — read-through for `resolveRiskPolicy`
|
|
302
|
+
* so the slow-moving merge-preset row isn't re-fetched on every gate evaluation. Absent →
|
|
303
|
+
* every resolve hits the repository (tests / no cache wired). Invalidated by
|
|
304
|
+
* `RiskPolicyService` on every preset write.
|
|
305
|
+
*/
|
|
306
|
+
riskPolicyCache?: GroupCacheHandle<RiskPolicyCacheValue>;
|
|
307
|
+
/**
|
|
308
|
+
* Optional: runs the gate-probe / merge GitHub reads under the run initiator's
|
|
309
|
+
* ambient context, so a per-user PAT (when set) is preferred over the deployment's
|
|
310
|
+
* App/env token (see `PatPreferringAppRegistry`). Absent → a pass-through
|
|
311
|
+
* (`(_, fn) => fn()`), so tests/conformance run unchanged.
|
|
312
|
+
*/
|
|
313
|
+
runInitiatorScope?: RunInitiatorScope;
|
|
314
|
+
/**
|
|
315
|
+
* Optional: files a GitHub issue / Jira ticket for the `tracker` step (the
|
|
316
|
+
* tech-debt recurring pipeline). Absent → the `tracker` step passes through
|
|
317
|
+
* without filing anything, so the engine works unchanged when no tracker is wired.
|
|
318
|
+
*/
|
|
319
|
+
ticketTrackerProvider?: TicketTrackerProvider;
|
|
320
|
+
/**
|
|
321
|
+
* Optional: writes back to a task's linked tracker issue(s) as its PR progresses
|
|
322
|
+
* (comment on PR open; comment + close as resolved on merge). Gated by the
|
|
323
|
+
* workspace's writeback settings + the per-task override. Absent → no writeback,
|
|
324
|
+
* so the engine works unchanged when no tracker writeback is wired.
|
|
325
|
+
*/
|
|
326
|
+
issueWriteback?: IssueWritebackProvider;
|
|
327
|
+
/**
|
|
328
|
+
* Optional: the recurring `bug-intake` step's read-and-claim helper. When wired, a `bug-intake`
|
|
329
|
+
* step pulls one matching open issue from the schedule's configured tracker board, claims it, and
|
|
330
|
+
* seeds the reused block from it; absent (no task sources wired) → the step is a no-op that
|
|
331
|
+
* completes the run without touching the block, so the engine works unchanged.
|
|
332
|
+
*/
|
|
333
|
+
bugIntakeService?: BugIntakeService;
|
|
334
|
+
/**
|
|
335
|
+
* Optional: the LLM observability sink. When wired, each emit rolls the per-run
|
|
336
|
+
* model-call aggregates onto the matching pipeline steps (`step.metrics`) so the
|
|
337
|
+
* board shows tokens / output-limit headroom / transport-vs-execution latency
|
|
338
|
+
* live. Absent (tests / unconfigured) → steps carry no `metrics`.
|
|
339
|
+
*/
|
|
340
|
+
llmObservability?: LlmObservabilityService;
|
|
341
|
+
/**
|
|
342
|
+
* Optional: resolve a block's run repo (installation + repo + default branch) bound to
|
|
343
|
+
* a checkout-free {@link RepoFiles} so a registered custom kind's pre/post-op hooks
|
|
344
|
+
* read/commit a targeted subset of the repo WITHOUT a checkout. A facade composes it
|
|
345
|
+
* from its wired `GitHubClient` + `resolveRepoTarget` (`makeResolveRunRepoContext`).
|
|
346
|
+
* Absent (tests / GitHub not connected) → pre/post-ops are skipped.
|
|
347
|
+
*/
|
|
348
|
+
resolveRunRepoContext?: ResolveRunRepoContext;
|
|
349
|
+
/**
|
|
350
|
+
* Optional: assert the workspace has a usable container-agent backend before a run
|
|
351
|
+
* starts (local mode delegating agents to a runner pool that isn't registered throws a
|
|
352
|
+
* clean {@link ConflictError} here). Absent → no start-time check (Cloudflare/Node have
|
|
353
|
+
* a fixed backend; a missing local pool still fails loudly at dispatch).
|
|
354
|
+
*/
|
|
355
|
+
assertAgentBackendConfigured?: (workspaceId: string) => Promise<void>;
|
|
356
|
+
/**
|
|
357
|
+
* Optional: writes the engine's verification report (CI verdict, tester report, ephemeral
|
|
358
|
+
* environment lifecycle, merge assessment, run metadata + an observability deep link) onto
|
|
359
|
+
* the run's pull request as a marker-delimited, idempotently-updated section. A facade
|
|
360
|
+
* composes it from its engine VCS client, so GitLab deployments publish too. Absent (tests,
|
|
361
|
+
* a no-VCS deployment) → the engine behaves exactly as it did before the feature.
|
|
362
|
+
*/
|
|
363
|
+
prVerificationReportPublisher?: PrVerificationReportPublisher;
|
|
364
|
+
/**
|
|
365
|
+
* Optional: the deployment's public SPA base URL, used to build the verification report's
|
|
366
|
+
* observability deep link. Absent → the report carries no link rather than a dead one.
|
|
367
|
+
*/
|
|
368
|
+
appBaseUrl?: string;
|
|
369
|
+
/**
|
|
370
|
+
* Optional: the per-workspace settings row, read by the verification-report hook for the
|
|
371
|
+
* `publishPrVerificationReport` opt-out. Absent ⇒ the default (on).
|
|
372
|
+
*/
|
|
373
|
+
workspaceSettingsRepository?: WorkspaceSettingsRepository;
|
|
374
|
+
/**
|
|
375
|
+
* Optional structured logger (the facade's pino logger) for the engine's best-effort paths —
|
|
376
|
+
* today the PR verification report, whose whole contract is that it never fails a run. Absent
|
|
377
|
+
* ⇒ those failures are silent, which is why every facade wires it.
|
|
378
|
+
*/
|
|
379
|
+
logger?: PrReportLogger;
|
|
380
|
+
}
|
|
381
|
+
//# sourceMappingURL=ExecutionServiceDependencies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExecutionServiceDependencies.d.ts","sourceRoot":"","sources":["../../../src/modules/execution/ExecutionServiceDependencies.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExecutionServiceDependencies.js","sourceRoot":"","sources":["../../../src/modules/execution/ExecutionServiceDependencies.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { BlockRepository, Clock, ExecutionInstance, PrVerificationReportPublisher, TaskRepository, WorkspaceSettingsRepository } from '@cat-factory/kernel';
|
|
2
|
+
/** Minimal structured logger (pino-compatible); optional, like every other best-effort path. */
|
|
3
|
+
export interface PrReportLogger {
|
|
4
|
+
warn(obj: Record<string, unknown>, msg?: string): void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The engine collaborator that keeps a run's **verification report** on its pull request.
|
|
8
|
+
*
|
|
9
|
+
* Shape decision (see `docs/initiatives/pr-verification-report.md`, D2): this is an engine
|
|
10
|
+
* HOOK on step settlement, not a pipeline step — so it is pipeline-shape agnostic (a
|
|
11
|
+
* deployment-authored pipeline gets a report without editing it) and a run that fails or
|
|
12
|
+
* parks part-way still leaves its evidence on the PR. `RunDispatcher.recordStepResult` calls
|
|
13
|
+
* {@link publishForRun} once per settled step, positioned AFTER the terminal step resolver
|
|
14
|
+
* (so the `merger` step's publish carries the resolved `MergeDecision`) and BEFORE
|
|
15
|
+
* `finalizeBlock` (so the `pipeline_complete` card a merger-less pipeline raises points at a
|
|
16
|
+
* PR that already carries the finished report). A passing polling gate settles through the
|
|
17
|
+
* same `recordStepResult`, so the CI verdict needs no hook of its own.
|
|
18
|
+
*
|
|
19
|
+
* Everything it reports is already in memory on the {@link ExecutionInstance}; the only reads
|
|
20
|
+
* are one block point-read and one batched `listByBlock` for the linked tracker issues — no
|
|
21
|
+
* N+1, and no re-probe of the CI/mergeability providers (which would cost a round trip and
|
|
22
|
+
* could disagree with the verdict the gate actually acted on).
|
|
23
|
+
*
|
|
24
|
+
* Every failure mode is a silent no-op: no publisher wired (tests, a no-VCS deployment), no
|
|
25
|
+
* PR yet, an unchanged report, or a transport error. Publishing a report must never fail a
|
|
26
|
+
* run that otherwise succeeded.
|
|
27
|
+
*/
|
|
28
|
+
export interface PrVerificationReportControllerDeps {
|
|
29
|
+
blockRepository: BlockRepository;
|
|
30
|
+
clock: Clock;
|
|
31
|
+
/**
|
|
32
|
+
* Optional: writes the rendered section onto the block's PR. Absent (no VCS client wired)
|
|
33
|
+
* ⇒ every publish is a no-op, so the engine behaves exactly as it did before this feature.
|
|
34
|
+
*/
|
|
35
|
+
publisher?: PrVerificationReportPublisher;
|
|
36
|
+
/** Optional: resolves the task's linked tracker issues in ONE batched read. */
|
|
37
|
+
taskRepository?: TaskRepository;
|
|
38
|
+
/**
|
|
39
|
+
* Optional: the per-workspace `publishPrVerificationReport` opt-out. Absent (or no saved
|
|
40
|
+
* settings row) ⇒ the default, which is ON — a deployment that wired a publisher wants the
|
|
41
|
+
* report.
|
|
42
|
+
*/
|
|
43
|
+
workspaceSettingsRepository?: WorkspaceSettingsRepository;
|
|
44
|
+
/**
|
|
45
|
+
* Optional: the deployment's public SPA base URL, used to build the observability deep
|
|
46
|
+
* link. Absent ⇒ the report's `observability.runUrl` is null and no link is rendered
|
|
47
|
+
* (better than emitting a link to nowhere).
|
|
48
|
+
*/
|
|
49
|
+
appBaseUrl?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Optional structured logger for the best-effort failure path. Wire it: publishing is the
|
|
52
|
+
* one part of the run that is DESIGNED to fail silently, so without a log a revoked token or
|
|
53
|
+
* a rejected body leaves no trace anywhere — the report simply stops appearing.
|
|
54
|
+
*/
|
|
55
|
+
logger?: PrReportLogger;
|
|
56
|
+
}
|
|
57
|
+
export declare class PrVerificationReportController {
|
|
58
|
+
private readonly deps;
|
|
59
|
+
/**
|
|
60
|
+
* The last section published per execution id, so settlements that change nothing a reader
|
|
61
|
+
* would see cost no PR edit at all.
|
|
62
|
+
*
|
|
63
|
+
* It does NOT collapse a run to one edit: the report carries a per-step state table, so most
|
|
64
|
+
* settlements genuinely do change it and the report tracks the run as it progresses — which
|
|
65
|
+
* is the intent ("rewritten in place as the run progresses"). What the cache removes is the
|
|
66
|
+
* repeat write from a replayed durable step, a re-poll, or a settlement whose evidence is
|
|
67
|
+
* identical to the last one.
|
|
68
|
+
*
|
|
69
|
+
* In-process only and deliberately so: it is a WRITE-AVOIDANCE cache, never a correctness
|
|
70
|
+
* mechanism — the marker splice is idempotent, so a cold process (or a peer replica) simply
|
|
71
|
+
* re-publishes the same section and the adapter's own unchanged-check suppresses the remote
|
|
72
|
+
* write.
|
|
73
|
+
*/
|
|
74
|
+
private readonly lastPublished;
|
|
75
|
+
/**
|
|
76
|
+
* Hard cap on {@link lastPublished}. A long-lived Node replica serves an unbounded number of
|
|
77
|
+
* runs, and each entry holds a rendered section, so the map is bounded here rather than
|
|
78
|
+
* relying on a call site to evict finished runs (a coupling that would silently leak the
|
|
79
|
+
* moment a new terminal path forgot to call it). Oldest-first eviction: `Map` preserves
|
|
80
|
+
* insertion order, and evicting an entry only costs one redundant (idempotent) republish.
|
|
81
|
+
*/
|
|
82
|
+
private static readonly MAX_TRACKED_RUNS;
|
|
83
|
+
constructor(deps: PrVerificationReportControllerDeps);
|
|
84
|
+
/**
|
|
85
|
+
* Compose the report for `instance` and upsert it onto the run's PR. Best-effort: returns
|
|
86
|
+
* silently on every skip/failure path.
|
|
87
|
+
*/
|
|
88
|
+
publishForRun(workspaceId: string, instance: ExecutionInstance): Promise<void>;
|
|
89
|
+
/** Record the published fingerprint, evicting the oldest entry once the cap is reached. */
|
|
90
|
+
private remember;
|
|
91
|
+
/**
|
|
92
|
+
* The workspace's opt-out. Checked BEFORE anything is read or composed, so a workspace that
|
|
93
|
+
* turned the report off pays nothing for the hook. A workspace with no saved settings row
|
|
94
|
+
* reads as the default (on).
|
|
95
|
+
*/
|
|
96
|
+
private publishingEnabled;
|
|
97
|
+
private linkedIssues;
|
|
98
|
+
/**
|
|
99
|
+
* The deep link into the run's observability panel (Model activity / Provided context).
|
|
100
|
+
* The SPA is a single canvas, so the target is the board with the run's view params — see
|
|
101
|
+
* `useRunDeepLink` in `@cat-factory/app`, and slice 4 of the global-search initiative,
|
|
102
|
+
* which will generalise the parser.
|
|
103
|
+
*/
|
|
104
|
+
private runUrl;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=PrVerificationReportController.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PrVerificationReportController.d.ts","sourceRoot":"","sources":["../../../src/modules/execution/PrVerificationReportController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,KAAK,EACL,iBAAiB,EAEjB,6BAA6B,EAC7B,cAAc,EACd,2BAA2B,EAC5B,MAAM,qBAAqB,CAAA;AAI5B,gGAAgG;AAChG,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACvD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,kCAAkC;IACjD,eAAe,EAAE,eAAe,CAAA;IAChC,KAAK,EAAE,KAAK,CAAA;IACZ;;;OAGG;IACH,SAAS,CAAC,EAAE,6BAA6B,CAAA;IACzC,+EAA+E;IAC/E,cAAc,CAAC,EAAE,cAAc,CAAA;IAC/B;;;;OAIG;IACH,2BAA2B,CAAC,EAAE,2BAA2B,CAAA;IACzD;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,MAAM,CAAC,EAAE,cAAc,CAAA;CACxB;AAED,qBAAa,8BAA8B;IA2B7B,OAAO,CAAC,QAAQ,CAAC,IAAI;IA1BjC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA4B;IAE1D;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAM;IAE9C,YAA6B,IAAI,EAAE,kCAAkC,EAAI;IAEzE;;;OAGG;IACG,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwCnF;IAED,2FAA2F;IAC3F,OAAO,CAAC,QAAQ;IAUhB;;;;OAIG;YACW,iBAAiB;YAOjB,YAAY;IAY1B;;;;;OAKG;IACH,OAAO,CAAC,MAAM;CAef"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { DEFAULT_WORKSPACE_SETTINGS } from '@cat-factory/kernel';
|
|
2
|
+
import { composePrVerificationReport, renderPrVerificationReport } from './prReport.logic.js';
|
|
3
|
+
export class PrVerificationReportController {
|
|
4
|
+
deps;
|
|
5
|
+
/**
|
|
6
|
+
* The last section published per execution id, so settlements that change nothing a reader
|
|
7
|
+
* would see cost no PR edit at all.
|
|
8
|
+
*
|
|
9
|
+
* It does NOT collapse a run to one edit: the report carries a per-step state table, so most
|
|
10
|
+
* settlements genuinely do change it and the report tracks the run as it progresses — which
|
|
11
|
+
* is the intent ("rewritten in place as the run progresses"). What the cache removes is the
|
|
12
|
+
* repeat write from a replayed durable step, a re-poll, or a settlement whose evidence is
|
|
13
|
+
* identical to the last one.
|
|
14
|
+
*
|
|
15
|
+
* In-process only and deliberately so: it is a WRITE-AVOIDANCE cache, never a correctness
|
|
16
|
+
* mechanism — the marker splice is idempotent, so a cold process (or a peer replica) simply
|
|
17
|
+
* re-publishes the same section and the adapter's own unchanged-check suppresses the remote
|
|
18
|
+
* write.
|
|
19
|
+
*/
|
|
20
|
+
lastPublished = new Map();
|
|
21
|
+
/**
|
|
22
|
+
* Hard cap on {@link lastPublished}. A long-lived Node replica serves an unbounded number of
|
|
23
|
+
* runs, and each entry holds a rendered section, so the map is bounded here rather than
|
|
24
|
+
* relying on a call site to evict finished runs (a coupling that would silently leak the
|
|
25
|
+
* moment a new terminal path forgot to call it). Oldest-first eviction: `Map` preserves
|
|
26
|
+
* insertion order, and evicting an entry only costs one redundant (idempotent) republish.
|
|
27
|
+
*/
|
|
28
|
+
static MAX_TRACKED_RUNS = 256;
|
|
29
|
+
constructor(deps) {
|
|
30
|
+
this.deps = deps;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Compose the report for `instance` and upsert it onto the run's PR. Best-effort: returns
|
|
34
|
+
* silently on every skip/failure path.
|
|
35
|
+
*/
|
|
36
|
+
async publishForRun(workspaceId, instance) {
|
|
37
|
+
const publisher = this.deps.publisher;
|
|
38
|
+
if (!publisher)
|
|
39
|
+
return;
|
|
40
|
+
try {
|
|
41
|
+
if (!(await this.publishingEnabled(workspaceId)))
|
|
42
|
+
return;
|
|
43
|
+
// Ask the adapter WHERE this would go before composing anything: it both short-circuits a
|
|
44
|
+
// run that has no PR yet (most runs, for most of their life) and supplies the repo +
|
|
45
|
+
// provider the report states — the same resolution the write itself uses, so the report
|
|
46
|
+
// can never name a different repo from the one it lands on.
|
|
47
|
+
const target = await publisher.resolveTarget(workspaceId, instance.blockId);
|
|
48
|
+
if (!target)
|
|
49
|
+
return;
|
|
50
|
+
const block = await this.deps.blockRepository.get(workspaceId, instance.blockId);
|
|
51
|
+
// Only a task carries an implementation PR; a frame/module run has nothing to report on.
|
|
52
|
+
if (!block)
|
|
53
|
+
return;
|
|
54
|
+
const section = renderPrVerificationReport(composePrVerificationReport(instance, {
|
|
55
|
+
block,
|
|
56
|
+
issues: await this.linkedIssues(workspaceId, instance.blockId),
|
|
57
|
+
repo: target.repo,
|
|
58
|
+
provider: target.provider,
|
|
59
|
+
runUrl: this.runUrl(workspaceId, instance),
|
|
60
|
+
now: this.deps.clock.now(),
|
|
61
|
+
}));
|
|
62
|
+
// `generatedAt` changes on every compose, so compare the section with it masked out —
|
|
63
|
+
// otherwise the cache would never hit and every step would edit the PR.
|
|
64
|
+
const fingerprint = section.replaceAll(/"generatedAt": \d+/g, '"generatedAt": 0');
|
|
65
|
+
if (this.lastPublished.get(instance.id) === fingerprint)
|
|
66
|
+
return;
|
|
67
|
+
await publisher.publish(workspaceId, block.id, section);
|
|
68
|
+
this.remember(instance.id, fingerprint);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
// A PR-report write is bookkeeping. A provider outage, a revoked token, or a PR someone
|
|
72
|
+
// closed underneath the run must never turn a green run red.
|
|
73
|
+
this.deps.logger?.warn({ err: error, executionId: instance.id, blockId: instance.blockId, workspaceId }, 'Failed to publish the PR verification report');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** Record the published fingerprint, evicting the oldest entry once the cap is reached. */
|
|
77
|
+
remember(executionId, fingerprint) {
|
|
78
|
+
this.lastPublished.delete(executionId);
|
|
79
|
+
this.lastPublished.set(executionId, fingerprint);
|
|
80
|
+
while (this.lastPublished.size > PrVerificationReportController.MAX_TRACKED_RUNS) {
|
|
81
|
+
const oldest = this.lastPublished.keys().next();
|
|
82
|
+
if (oldest.done)
|
|
83
|
+
break;
|
|
84
|
+
this.lastPublished.delete(oldest.value);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The workspace's opt-out. Checked BEFORE anything is read or composed, so a workspace that
|
|
89
|
+
* turned the report off pays nothing for the hook. A workspace with no saved settings row
|
|
90
|
+
* reads as the default (on).
|
|
91
|
+
*/
|
|
92
|
+
async publishingEnabled(workspaceId) {
|
|
93
|
+
const repo = this.deps.workspaceSettingsRepository;
|
|
94
|
+
if (!repo)
|
|
95
|
+
return true;
|
|
96
|
+
const settings = (await repo.get(workspaceId)) ?? DEFAULT_WORKSPACE_SETTINGS;
|
|
97
|
+
return settings.publishPrVerificationReport;
|
|
98
|
+
}
|
|
99
|
+
async linkedIssues(workspaceId, blockId) {
|
|
100
|
+
const repo = this.deps.taskRepository;
|
|
101
|
+
if (!repo)
|
|
102
|
+
return [];
|
|
103
|
+
const records = await repo.listByBlock(workspaceId, blockId);
|
|
104
|
+
return records.map((record) => ({
|
|
105
|
+
source: record.source,
|
|
106
|
+
externalId: record.externalId,
|
|
107
|
+
title: record.title,
|
|
108
|
+
url: record.url,
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The deep link into the run's observability panel (Model activity / Provided context).
|
|
113
|
+
* The SPA is a single canvas, so the target is the board with the run's view params — see
|
|
114
|
+
* `useRunDeepLink` in `@cat-factory/app`, and slice 4 of the global-search initiative,
|
|
115
|
+
* which will generalise the parser.
|
|
116
|
+
*/
|
|
117
|
+
runUrl(workspaceId, instance) {
|
|
118
|
+
const base = this.deps.appBaseUrl?.trim();
|
|
119
|
+
if (!base)
|
|
120
|
+
return null;
|
|
121
|
+
// Built by hand rather than with `URLSearchParams`: orchestration is runtime-neutral and
|
|
122
|
+
// compiles without the DOM lib, so the global isn't in its type surface.
|
|
123
|
+
const query = Object.entries({
|
|
124
|
+
ws: workspaceId,
|
|
125
|
+
block: instance.blockId,
|
|
126
|
+
run: instance.id,
|
|
127
|
+
view: 'observability',
|
|
128
|
+
})
|
|
129
|
+
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
|
|
130
|
+
.join('&');
|
|
131
|
+
return `${base.replace(/\/$/, '')}/?${query}`;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=PrVerificationReportController.js.map
|