@cat-factory/server 0.325.1 → 0.326.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.
Files changed (43) hide show
  1. package/dist/agents/CompositeAgentExecutor.d.ts +22 -3
  2. package/dist/agents/CompositeAgentExecutor.d.ts.map +1 -1
  3. package/dist/agents/CompositeAgentExecutor.js +91 -5
  4. package/dist/agents/CompositeAgentExecutor.js.map +1 -1
  5. package/dist/agents/DelegatedAgentExecutor.d.ts +147 -0
  6. package/dist/agents/DelegatedAgentExecutor.d.ts.map +1 -0
  7. package/dist/agents/DelegatedAgentExecutor.js +537 -0
  8. package/dist/agents/DelegatedAgentExecutor.js.map +1 -0
  9. package/dist/agents/brief.d.ts +61 -0
  10. package/dist/agents/brief.d.ts.map +1 -0
  11. package/dist/agents/brief.js +94 -0
  12. package/dist/agents/brief.js.map +1 -0
  13. package/dist/agents/containerJobAddressing.d.ts +1 -20
  14. package/dist/agents/containerJobAddressing.d.ts.map +1 -1
  15. package/dist/agents/containerJobAddressing.js +5 -23
  16. package/dist/agents/containerJobAddressing.js.map +1 -1
  17. package/dist/agents/delegatedExecutorHost.d.ts +90 -0
  18. package/dist/agents/delegatedExecutorHost.d.ts.map +1 -0
  19. package/dist/agents/delegatedExecutorHost.js +124 -0
  20. package/dist/agents/delegatedExecutorHost.js.map +1 -0
  21. package/dist/agents/delegationCredentials.d.ts +20 -0
  22. package/dist/agents/delegationCredentials.d.ts.map +1 -0
  23. package/dist/agents/delegationCredentials.js +63 -0
  24. package/dist/agents/delegationCredentials.js.map +1 -0
  25. package/dist/agents/delegationWorkBranch.d.ts +31 -0
  26. package/dist/agents/delegationWorkBranch.d.ts.map +1 -0
  27. package/dist/agents/delegationWorkBranch.js +103 -0
  28. package/dist/agents/delegationWorkBranch.js.map +1 -0
  29. package/dist/agents/jobBody.d.ts.map +1 -1
  30. package/dist/agents/jobBody.js +8 -9
  31. package/dist/agents/jobBody.js.map +1 -1
  32. package/dist/index.d.ts +3 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +7 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/modules/publicApi/openapiDocument.generated.d.ts +1 -1
  37. package/dist/modules/publicApi/openapiDocument.generated.d.ts.map +1 -1
  38. package/dist/modules/publicApi/openapiDocument.generated.js +1 -1
  39. package/dist/modules/publicApi/openapiDocument.generated.js.map +1 -1
  40. package/dist/modules/workspaces/WorkspaceController.d.ts.map +1 -1
  41. package/dist/modules/workspaces/WorkspaceController.js +42 -1
  42. package/dist/modules/workspaces/WorkspaceController.js.map +1 -1
  43. package/package.json +14 -14
@@ -0,0 +1,537 @@
1
+ import { ConflictError, UnavailableError, describeError, getErrorMessage, stepJobId, } from '@cat-factory/kernel';
2
+ import { delegatedExecutorFor } from '@cat-factory/agents';
3
+ import { resolveAprioriWorkingBranch } from '@cat-factory/contracts';
4
+ import { composeDelegationBrief, briefRepoProvider } from './brief.js';
5
+ import { ensureDelegatedWorkBranch } from './delegationWorkBranch.js';
6
+ import { recordAgentContextSnapshot } from './agentContextRecord.js';
7
+ import { resolveDelegationCredentials } from './delegationCredentials.js';
8
+ /**
9
+ * Runs a `delegated`-surface step on a registered external executor.
10
+ *
11
+ * Async by construction: an external run is polled, never awaited, which is the same shape the
12
+ * container executor has and the reason both durable drivers already know how to drive it.
13
+ */
14
+ export class DelegatedAgentExecutor {
15
+ deps;
16
+ log;
17
+ /**
18
+ * Built executors, memoised per id for the life of this instance.
19
+ *
20
+ * A definition's `create` may build a client, and this executor is constructed once per app
21
+ * (per isolate on the Worker), so building per dispatch would discard whatever an executor
22
+ * warmed. Memoising on the INSTANCE rather than a module map is what keeps it out of the
23
+ * module-global trap every registry in this codebase is built to avoid.
24
+ */
25
+ built = new Map();
26
+ constructor(deps) {
27
+ this.deps = deps;
28
+ this.log = deps.logger.child({ component: 'delegatedAgent' });
29
+ }
30
+ /** Every delegated step is polled: an external run outlives any request of ours. */
31
+ runsAsync(_context) {
32
+ return true;
33
+ }
34
+ /**
35
+ * A delegated step resolves NO model, and that is the honest answer rather than a gap.
36
+ *
37
+ * The external executor picks its own, through its own credentials, and the platform never sees
38
+ * it. Returning a guess would put a model on the board that nothing ran, and every reader
39
+ * downstream (the run card, the spend rollup, the debug overview) would treat it as the thing
40
+ * that spent the tokens it also cannot see.
41
+ */
42
+ resolveModel(_context) {
43
+ return Promise.resolve(undefined);
44
+ }
45
+ /**
46
+ * Not quota-based. A delegated run spends nothing of OURS: no pooled subscription token is
47
+ * leased and no proxy call is metered, so it is neither quota nor budget.
48
+ */
49
+ isQuotaBased(_context) {
50
+ return Promise.resolve(false);
51
+ }
52
+ /**
53
+ * Start (or RE-ATTACH to) this step's external work.
54
+ *
55
+ * Idempotent per correlation key, which is what the port asks of every executor and what the
56
+ * engine's claim-before-effect makes possible on this side: the key is derived from the run, the
57
+ * kind and the dispatch epoch, so a replayed dispatch produces the same key and the executor is
58
+ * asked to recognise its own run rather than start another.
59
+ */
60
+ async startJob(context) {
61
+ const { workspaceId, executionId, blockId } = requireIds(context);
62
+ const definition = this.requireDefinition(context.agentKind);
63
+ const executor = this.executorFor(definition);
64
+ const correlationKey = stepJobId(executionId, context.agentKind, context.dispatchEpoch);
65
+ const { brief, aprioriWork } = await this.composeBrief(context, {
66
+ workspaceId,
67
+ executionId,
68
+ blockId,
69
+ correlationKey,
70
+ });
71
+ const jobLog = this.log.child({
72
+ workspaceId,
73
+ executionId,
74
+ jobId: correlationKey,
75
+ agentKind: context.agentKind,
76
+ executor: definition.id,
77
+ });
78
+ const credentials = await resolveDelegationCredentials({
79
+ definition,
80
+ workspaceId,
81
+ blockId,
82
+ resolveToolSecrets: this.deps.resolveToolSecrets,
83
+ logger: jobLog,
84
+ });
85
+ // After everything the platform can refuse on its own (the repo, the brief, the credentials),
86
+ // so a step that was never going to dispatch leaves no ref behind. It cannot move later: a CI
87
+ // system handed a branch that is not there fails at checkout rather than reporting anything
88
+ // this platform can act on, so `start` must find it already made. What that ordering does NOT
89
+ // buy is a ref-free failure INSIDE the dispatch; a create followed by an executor that refuses
90
+ // leaves the branch, and the next attempt re-uses it.
91
+ await this.prepareWorkBranch(definition, brief, aprioriWork, jobLog);
92
+ let started;
93
+ try {
94
+ started = await executor.start(brief, credentials);
95
+ }
96
+ catch (error) {
97
+ jobLog.warn('delegated executor refused the dispatch', describeError(error));
98
+ throw new UnavailableError(`The "${definition.id}" executor could not start this step: ${getErrorMessage(error)}`, 'delegated_executor_failed', { executor: definition.id });
99
+ }
100
+ if (!started.externalId) {
101
+ // Refused rather than recorded, because a dispatch with no external id can never be polled:
102
+ // the step would park on a job nothing can settle and fail hours later on its poll budget,
103
+ // naming a timeout instead of the contract the executor broke.
104
+ throw new UnavailableError(`The "${definition.id}" executor started this step and returned no external id, so the ` +
105
+ 'platform has nothing to poll. An executor whose system reports no id at start is ' +
106
+ "expected to recover one by the brief's correlation key before returning.", 'delegated_executor_failed', { executor: definition.id });
107
+ }
108
+ jobLog.info('delegated work started', {
109
+ externalId: started.externalId,
110
+ ...(started.url ? { externalUrl: started.url } : {}),
111
+ });
112
+ // The same snapshot a container dispatch files, from the same allow-list: what the agent was
113
+ // told is auditable through `/api/v1/debug/runs/:runId/agent-context` whoever ran it. The
114
+ // credentials resolved above are deliberately not in scope of the body handed to it.
115
+ await recordAgentContextSnapshot(this.deps.agentContextObservability, jobLog, {
116
+ context,
117
+ body: snapshotBody(brief),
118
+ // There is no model to name: the executor chose one we never saw. Naming the EXECUTOR under
119
+ // a provider of `delegated` says exactly that, where a blank would read as a dispatch that
120
+ // resolved nothing and a borrowed default would name a model that ran nowhere.
121
+ model: `delegated:${definition.id}`,
122
+ workspaceId,
123
+ executionId,
124
+ });
125
+ return {
126
+ jobId: correlationKey,
127
+ runId: executionId,
128
+ workspaceId,
129
+ // The block, so the POLL resolves this executor's credentials in the scope the dispatch
130
+ // did. The engine re-supplies it from the run, but a handle that answers for itself is
131
+ // what keeps a non-durable caller (`run()`'s own drive, a test) on the same path.
132
+ blockId,
133
+ agentKind: context.agentKind,
134
+ ...(context.initiatedByUserId ? { initiatedByUserId: context.initiatedByUserId } : {}),
135
+ repo: {
136
+ owner: brief.repo.owner,
137
+ name: brief.repo.name,
138
+ baseBranch: brief.branches.base,
139
+ provider: brief.repo.provider,
140
+ },
141
+ delegated: {
142
+ executor: definition.id,
143
+ externalId: started.externalId,
144
+ ...(started.url ? { url: started.url } : {}),
145
+ // What a later poll cannot derive: a handle carries the run, and the work branch is named
146
+ // from the BLOCK. An executor reading back what its own system produced needs it, and
147
+ // guessing puts the wrong pull request on the block.
148
+ branches: brief.branches,
149
+ // The repo the WORK targets, for the same reason and with a sharper edge: it is routinely
150
+ // not the repo the executor's own job lives in (an automation repo dispatching against many
151
+ // product repos is the ordinary shape), so an executor reading back what it produced from
152
+ // its OWN configured repository finds nothing on every such deployment.
153
+ repo: { owner: brief.repo.owner, name: brief.repo.name },
154
+ },
155
+ };
156
+ }
157
+ /**
158
+ * Poll the external work, re-resolving the executor's credentials FIRST.
159
+ *
160
+ * Re-resolved rather than carried on the handle, and that is not a caching miss: a delegated
161
+ * poll can run hours after the dispatch, a GitHub App token lives one hour, and a credential
162
+ * frozen at dispatch would be dead exactly on the long runs this executor class exists for.
163
+ */
164
+ async pollJob(handle) {
165
+ const delegated = handle.delegated;
166
+ if (!delegated) {
167
+ throw new ConflictError('A delegated poll arrived with no delegation on its handle, so there is nothing to ' +
168
+ 'address. The step records which executor it dispatched to; a handle without it was ' +
169
+ 'rebuilt from a step that never claimed one.',
170
+ // The fault is in THIS RUN's state, not in what the deployment registers, so it does not
171
+ // borrow `delegated_executor_unwired`: that copy would send an operator to inspect a
172
+ // registration with nothing wrong in it.
173
+ 'delegated_claim_missing');
174
+ }
175
+ const scope = requireHandleScope(handle);
176
+ const definition = this.requireDefinition(scope.agentKind, delegated.executor);
177
+ const executor = this.executorFor(definition);
178
+ const jobLog = this.log.child({
179
+ workspaceId: scope.workspaceId,
180
+ executionId: scope.runId,
181
+ jobId: handle.jobId,
182
+ agentKind: scope.agentKind,
183
+ executor: definition.id,
184
+ });
185
+ const target = {
186
+ executor: definition.id,
187
+ correlationKey: handle.jobId,
188
+ externalId: delegated.externalId,
189
+ ...(delegated.url ? { url: delegated.url } : {}),
190
+ ...(delegated.branches ? { branches: delegated.branches } : {}),
191
+ ...(delegated.repo ? { repo: delegated.repo } : {}),
192
+ ...scope,
193
+ };
194
+ const credentials = await resolveDelegationCredentials({
195
+ definition,
196
+ workspaceId: scope.workspaceId,
197
+ // The SAME scope the dispatch resolved in. Dropped here, a deployment whose credential
198
+ // store is per service resolves an empty bag on every poll of a run it started perfectly
199
+ // well, and the step dies on an unreadable status while the external work carries on.
200
+ blockId: scope.blockId,
201
+ resolveToolSecrets: this.deps.resolveToolSecrets,
202
+ logger: jobLog,
203
+ });
204
+ const update = await executor.poll(target, credentials);
205
+ return toJobUpdate(update, definition, this.deps.clock.now());
206
+ }
207
+ /**
208
+ * Stop every delegated unit this run holds, and REPORT what that achieved.
209
+ *
210
+ * The report is the point. An executor that declares no `cancel` leaves its run alive: it will
211
+ * finish, open its pull request and bill its tokens long after the platform recorded this run as
212
+ * stopped, and the person who stopped it is the one who needs to know to go and stop it there.
213
+ * Best-effort throughout (a teardown must never propagate) but never SILENT: a failure to
214
+ * cancel is reported as "not cancelled" with its cause, which is a different fact from a clean
215
+ * stop and renders differently on the step.
216
+ */
217
+ async reclaimRun(target) {
218
+ const handles = target.delegations ?? [];
219
+ if (handles.length === 0)
220
+ return;
221
+ const delegations = await Promise.all(handles.map((handle) => this.cancelOne(handle)));
222
+ return { delegations };
223
+ }
224
+ /**
225
+ * A delegated step cannot be run SYNCHRONOUSLY: `run()` exists for non-durable callers and
226
+ * tests, and an external system that answers in hours is exactly what that shape cannot serve.
227
+ * Refused loudly rather than looped in-process, which would hold a request open for the duration
228
+ * of somebody else's CI.
229
+ */
230
+ run(context) {
231
+ return Promise.reject(new ConflictError(`The \`${context.agentKind}\` step runs on an external executor, which is polled rather ` +
232
+ 'than awaited. Drive it through the async path (`startJob` / `pollJob`).',
233
+ // NOT `delegated_executor_unwired`: the executor here is registered and correct, and that
234
+ // reason's copy tells an operator to go and register it. What is wrong is the drive path
235
+ // the caller chose.
236
+ 'delegated_step_async_only'));
237
+ }
238
+ async cancelOne(handle) {
239
+ const definition = this.deps.delegatedExecutorRegistry.get(handle.executor);
240
+ if (!definition) {
241
+ return {
242
+ correlationKey: handle.correlationKey,
243
+ cancelled: false,
244
+ note: `This deployment no longer registers the "${handle.executor}" executor, so the external work could not be stopped.`,
245
+ };
246
+ }
247
+ const executor = this.executorFor(definition);
248
+ if (!executor.cancel) {
249
+ return {
250
+ correlationKey: handle.correlationKey,
251
+ cancelled: false,
252
+ note: `The "${definition.id}" executor declares no cancel, so the external work was left running.`,
253
+ };
254
+ }
255
+ const jobLog = this.log.child({
256
+ workspaceId: handle.workspaceId,
257
+ executionId: handle.runId,
258
+ jobId: handle.correlationKey,
259
+ executor: definition.id,
260
+ });
261
+ try {
262
+ const credentials = await resolveDelegationCredentials({
263
+ definition,
264
+ workspaceId: handle.workspaceId,
265
+ // Scoped like the dispatch and the poll: a cancel resolving an empty bag reports work as
266
+ // possibly-still-running that this executor could have stopped.
267
+ blockId: handle.blockId,
268
+ resolveToolSecrets: this.deps.resolveToolSecrets,
269
+ logger: jobLog,
270
+ });
271
+ await executor.cancel(handle, credentials);
272
+ return { correlationKey: handle.correlationKey, cancelled: true };
273
+ }
274
+ catch (error) {
275
+ jobLog.warn('delegated cancel failed; the external work may still be running', {
276
+ ...describeError(error),
277
+ });
278
+ return {
279
+ correlationKey: handle.correlationKey,
280
+ cancelled: false,
281
+ note: `Stopping the external work failed (${getErrorMessage(error)}); it may still be running.`,
282
+ };
283
+ }
284
+ }
285
+ /** The registered definition for a kind, refusing the two ways it can be missing. */
286
+ requireDefinition(agentKind, knownExecutor) {
287
+ const id = knownExecutor ?? delegatedExecutorFor(agentKind, this.deps.agentKindRegistry);
288
+ if (!id) {
289
+ throw new ConflictError(`The \`${agentKind}\` step was routed to the delegated executor and declares none.`, 'delegated_executor_unwired');
290
+ }
291
+ const definition = this.deps.delegatedExecutorRegistry.get(id);
292
+ if (!definition) {
293
+ throw new ConflictError(`The \`${agentKind}\` step runs on the delegated executor "${id}", which this ` +
294
+ `deployment does not register` +
295
+ (this.deps.delegatedExecutorRegistry.size > 0
296
+ ? ` (registered: ${this.deps.delegatedExecutorRegistry.ids().join(', ')}).`
297
+ : '.'), 'delegated_executor_unwired');
298
+ }
299
+ return definition;
300
+ }
301
+ executorFor(definition) {
302
+ const existing = this.built.get(definition.id);
303
+ if (existing)
304
+ return existing;
305
+ const built = definition.create(this.deps.executorDeps);
306
+ this.built.set(definition.id, built);
307
+ return built;
308
+ }
309
+ /**
310
+ * Honour the executor's {@link DelegatedExecutorDefinition.workBranch} declaration.
311
+ *
312
+ * `executor-creates` writes nothing and probes nothing, which is what keeps a run whose external
313
+ * work never landed from leaving an empty ref behind.
314
+ */
315
+ async prepareWorkBranch(definition, brief, aprioriWork, jobLog) {
316
+ if (definition.workBranch !== 'platform-creates')
317
+ return;
318
+ await ensureDelegatedWorkBranch({ resolveRepoFiles: this.deps.resolveRepoFiles, logger: jobLog }, brief, { executorId: definition.id, apriori: aprioriWork });
319
+ }
320
+ /**
321
+ * Resolve the repo + branches this dispatch targets, then compose the brief over them.
322
+ *
323
+ * `aprioriWork` rides back out because the brief carries the branch NAME and not who is allowed
324
+ * to create it, and only this method knows which of the two answers produced the name.
325
+ */
326
+ async composeBrief(context, ids) {
327
+ const repo = await this.deps.resolveRepoTarget(ids.workspaceId, ids.blockId);
328
+ if (!repo) {
329
+ throw new ConflictError(`No connected repository found for workspace '${ids.workspaceId}'. A delegated step is ` +
330
+ 'still work on a repository: link one to this service before running it.', 'github_not_connected');
331
+ }
332
+ const origin = this.deps.resolveRepoOrigin(repo);
333
+ const trackerRef = await this.resolveTrackerRef(ids.workspaceId, ids.blockId);
334
+ // The task's own WORKING branch when it declared one, through the shared rule the container
335
+ // dispatch and the repo-ops controller resolve it with, so the three cannot drift on which
336
+ // branch a run builds inside (it also refuses a working branch equal to the base). Delegating
337
+ // the step changes nothing about that choice: the pull request, the `ci` gate and the merger
338
+ // all ride the branch the task named, so a dispatch onto `cat-factory/<blockId>` instead would
339
+ // land the external work on a ref nothing downstream looks at.
340
+ const aprioriWork = resolveAprioriWorkingBranch(context.aprioriBranches, repo.baseBranch);
341
+ const brief = composeDelegationBrief(context, this.deps.agentKindRegistry, {
342
+ correlationKey: ids.correlationKey,
343
+ workspaceId: ids.workspaceId,
344
+ blockId: ids.blockId,
345
+ runId: ids.executionId,
346
+ stepIndex: context.stepIndex ?? 0,
347
+ target: {
348
+ repo: {
349
+ owner: repo.owner,
350
+ name: repo.name,
351
+ cloneUrl: origin.cloneUrl,
352
+ provider: briefRepoProvider(repo.provider ?? origin.provider),
353
+ ...(repo.serviceDirectory ? { directory: repo.serviceDirectory } : {}),
354
+ },
355
+ branches: {
356
+ base: repo.baseBranch,
357
+ // The task's declared working branch, else the deterministic per-task branch every step
358
+ // of this run's pipeline shares: the same name the container path resolves, so a
359
+ // delegated producer and a later container fixer work on ONE branch. Whether the REF
360
+ // exists when the executor is called is its own `workBranch` declaration, honoured just
361
+ // before `start`.
362
+ work: aprioriWork ?? `cat-factory/${ids.blockId}`,
363
+ },
364
+ ...(trackerRef ? { trackerRef } : {}),
365
+ },
366
+ });
367
+ return { brief, aprioriWork: aprioriWork !== undefined };
368
+ }
369
+ /**
370
+ * Where the work came from, when it came from a tracker: ONE batch read of the block's linked
371
+ * issues, and the first live one.
372
+ *
373
+ * First rather than all, because the brief names the ticket the work is FOR and a block carries
374
+ * one such link by construction (`claimBlockLink` makes "one task per ticket" an invariant). Any
375
+ * further attachments are reference material, and they already reach the agent as context files.
376
+ */
377
+ async resolveTrackerRef(workspaceId, blockId) {
378
+ const repository = this.deps.taskRepository;
379
+ if (!repository)
380
+ return undefined;
381
+ const linked = await repository.listByBlock(workspaceId, blockId);
382
+ const first = linked[0];
383
+ if (!first)
384
+ return undefined;
385
+ return {
386
+ provider: first.source,
387
+ key: first.externalId,
388
+ ...(first.url ? { url: first.url } : {}),
389
+ };
390
+ }
391
+ }
392
+ /**
393
+ * The four run facts a poll addresses by, refused rather than defaulted when the handle carries
394
+ * none.
395
+ *
396
+ * They are optional on {@link AgentJobHandle} because the container executor does not need them
397
+ * all, and REQUIRED on {@link DelegationHandle} because a delegated call does. Filling the gap
398
+ * with `''` is what makes that requirement a lie, and every consequence of it is silent:
399
+ * credentials resolved without the block come back empty on a per-service store, so a poll of a
400
+ * perfectly healthy external run cannot read its status and the step dies hours later reporting a
401
+ * timeout; and a deployment whose executor picks which workflow to address from the scope it is
402
+ * handed (its repository, its agent kind) would dispatch against one and then poll another,
403
+ * reporting a live run as one that never appeared. The engine supplies all four at the one place
404
+ * a handle is rebuilt (`pollHandleFor`), so an absent one is a new call site rather than a state a
405
+ * deployment can reach.
406
+ */
407
+ function requireHandleScope(handle) {
408
+ const { workspaceId, blockId, runId, agentKind } = handle;
409
+ if (!workspaceId || !blockId || !runId || !agentKind) {
410
+ throw new ConflictError('A delegated poll arrived with an incomplete handle (it needs the workspace, the block, ' +
411
+ 'the run and the agent kind), so neither the credentials this executor declares nor the ' +
412
+ 'external work it addresses can be resolved in the scope its dispatch used. The engine ' +
413
+ 'supplies all four from the run; a handle without them was built somewhere that does not.', 'delegated_claim_missing', { missing: missingScopeFields(handle) });
414
+ }
415
+ return { workspaceId, blockId, runId, agentKind };
416
+ }
417
+ /** Which of the four a handle was short, so the refusal names the call site's actual gap. */
418
+ function missingScopeFields(handle) {
419
+ return ['workspaceId', 'blockId', 'runId', 'agentKind'].filter((field) => !handle[field]);
420
+ }
421
+ /** The delegated dispatch's ids, refused together so a caller cannot proceed on half of them. */
422
+ function requireIds(context) {
423
+ const { workspaceId, executionId } = context;
424
+ const blockId = context.block.id;
425
+ if (!workspaceId || !executionId || !blockId) {
426
+ throw new Error('DelegatedAgentExecutor requires workspaceId, executionId and block.id');
427
+ }
428
+ return { workspaceId, executionId, blockId };
429
+ }
430
+ /**
431
+ * The brief, shaped as the agent-context snapshot's allow-list reads it.
432
+ *
433
+ * It reuses that allow-list rather than adding a second projection, because the rule it enforces
434
+ * (copy the prompts, the fragment bodies and the context files; never a credential) is exactly as
435
+ * binding here. `mode: 'delegated'` is what tells a reader of the snapshot that no container ran.
436
+ */
437
+ function snapshotBody(brief) {
438
+ return {
439
+ mode: 'delegated',
440
+ systemPrompt: brief.systemPrompt,
441
+ userPrompt: brief.userPrompt,
442
+ contextFiles: brief.contextFiles,
443
+ repo: { owner: brief.repo.owner, name: brief.repo.name, baseBranch: brief.branches.base },
444
+ branch: brief.branches.work,
445
+ };
446
+ }
447
+ /**
448
+ * Map an executor's own answer into the engine's poll vocabulary.
449
+ *
450
+ * The four places this is NOT a rename:
451
+ *
452
+ * - `retryable` decides whether a failure is re-driven on the job-failure budget or is terminal.
453
+ * An executor that says nothing means TERMINAL, because a verdict its own system called final is
454
+ * not something a second dispatch improves on, and the alternative spends the budget re-running
455
+ * somebody else's CI to reach the same answer. It travels on the DELEGATED channel, under a name
456
+ * that describes a delegated step, never on the container path's `harnessShutdown`.
457
+ * - `usage` rides straight through, and its ABSENCE is the honest state for an executor that does
458
+ * not report it. Nothing here invents a zero: a zero would be summed into the run's total and
459
+ * read as work that cost nothing.
460
+ * - `externalId` and `url` are the two facts a poll can LEARN, for a system that answers its
461
+ * dispatch with neither (`workflow_dispatch` replies 204). Carried back so the record stops
462
+ * addressing the run by the correlation key: left uncarried, every later poll re-ran the bounded
463
+ * scan that recovered it, and on a busy repository the run scrolls off that page mid-flight and
464
+ * a live external run reads as one that never appeared.
465
+ * - `lastActivityAt` gains a FLOOR the executor did not report, because the poll itself is the
466
+ * evidence. This is the one field where "the executor said nothing" and "nothing is happening"
467
+ * are different facts and only one of them is true.
468
+ * - `branch` is carried rather than dropped: it is the entire product of an executor that pushes
469
+ * without opening a pull request, a case the port names as legitimate.
470
+ *
471
+ * `polledAt` is a parameter rather than a captured clock so this stays a pure mapping the tests can
472
+ * drive at a fixed instant.
473
+ */
474
+ function toJobUpdate(update, definition, polledAt) {
475
+ if (update.state === 'running') {
476
+ const learned = {
477
+ ...(update.externalId ? { externalId: update.externalId } : {}),
478
+ ...(update.url ? { url: update.url } : {}),
479
+ };
480
+ return {
481
+ state: 'running',
482
+ ...(update.phase ? { phase: update.phase } : {}),
483
+ // A successful poll IS the sign of life, so the poll's own clock is the floor when the
484
+ // executor reports no timestamp of its own. Without it a long, quiet external run (an Actions
485
+ // run sitting at `in_progress` reports an identical answer every time) folds no change at all,
486
+ // nothing persists, the step's `lastActivityAt` and the run's `updated_at` freeze at the first
487
+ // poll, and the stale-run sweeper re-collects a run that is perfectly alive. The engine's
488
+ // existing throttle decides how often that actually lands.
489
+ lastActivityAt: update.lastActivityAt ?? polledAt,
490
+ ...(Object.keys(learned).length > 0 ? { delegated: learned } : {}),
491
+ // The executor's own name, so the run diagnostics say where the step ran rather than
492
+ // reporting the container backend a delegated step never had.
493
+ backend: `delegated:${definition.id}`,
494
+ };
495
+ }
496
+ if (update.state === 'failed') {
497
+ // A NON-retryable failure is TERMINAL: the driver must not spend a recovery budget re-running
498
+ // somebody else's CI to reach the verdict it already reached. A retryable one buys exactly one
499
+ // fresh dispatch (`MAX_DELEGATED_RETRIES`). Reported on the DELEGATED channel rather than as
500
+ // `harnessShutdown`, which is the container path's signal for the same disposition under a
501
+ // name that misdescribes this one: a delegated step never had a harness, and the operator was
502
+ // shown "Harness shut down" for an external workflow that simply failed.
503
+ return {
504
+ state: 'failed',
505
+ error: update.error,
506
+ ...(update.detail ? { detail: update.detail } : {}),
507
+ delegated: {
508
+ ...(update.url ? { url: update.url } : {}),
509
+ // Stated on every delegated failure, both ways round. Read as "absent means retryable"
510
+ // the engine had one flag with an unstated default and no re-drive behind it, so an
511
+ // executor's `retryable: true` was silently the same as a final verdict.
512
+ disposition: update.retryable === true ? 'retryable' : 'terminal',
513
+ },
514
+ backend: `delegated:${definition.id}`,
515
+ };
516
+ }
517
+ const result = update.result;
518
+ return {
519
+ state: 'done',
520
+ result: {
521
+ output: result.summary,
522
+ ...(result.pullRequest ? { pullRequest: result.pullRequest } : {}),
523
+ ...(result.custom !== undefined ? { custom: result.custom } : {}),
524
+ // Tagged `subscription`, never `metered`. The tokens were spent on the EXECUTOR's own
525
+ // account, so counting them against this workspace's budget would pause runs over money the
526
+ // platform did not spend. `subscription` is the disposition that already means exactly this:
527
+ // recorded for the usage report, excluded from every spend rollup.
528
+ ...(result.usage ? { usage: result.usage, usageBilling: 'subscription' } : {}),
529
+ },
530
+ // The branch the work LANDED on, when the executor pushed without opening a pull request. The
531
+ // port names that case as legitimate, and the engine settles it onto the delegation record:
532
+ // dropped here, such a run reports done with no product anywhere, which reads exactly like a
533
+ // run that produced nothing.
534
+ ...(result.branch ? { delegated: { branch: result.branch } } : {}),
535
+ };
536
+ }
537
+ //# sourceMappingURL=DelegatedAgentExecutor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DelegatedAgentExecutor.js","sourceRoot":"","sources":["../../src/agents/DelegatedAgentExecutor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ import type { AgentDispatchContext, AgentRunContext, DelegationBrief, InjectedContextFile, VcsProvider } from '@cat-factory/kernel';
2
+ import { type AgentKindRegistry } from '@cat-factory/agents';
3
+ /** The half of an agent's instructions that is the same whatever executes the step. */
4
+ export interface AgentBriefCore {
5
+ /**
6
+ * The role prompt with the workspace's override applied, the block's best-practice standards
7
+ * folded at this kind's verbosity, and its trait guidance. NOT the finished container system
8
+ * prompt: the container-dispatch directives, the follow-up sentinel, the skills fold and the
9
+ * tool-server section are layered onto this at the container chokepoint.
10
+ */
11
+ roleSystemPrompt: string;
12
+ /** The generic block-context prompt (or the kind's own), with its wrappers already applied. */
13
+ userPrompt: string;
14
+ /** The `.cat-context/` files this dispatch was prepared with. */
15
+ contextFiles: InjectedContextFile[];
16
+ }
17
+ /**
18
+ * Compose the executor-independent half of a dispatch's instructions.
19
+ *
20
+ * `dispatch` carries the resolved checkout facts for the kinds whose own prompt names a branch.
21
+ * Both callers have one (a delegated executor gets a checkout too: its own), which is the
22
+ * difference between this and an inline caller, and it is why the parameter is required here
23
+ * rather than optional as it is on `userPromptFor`.
24
+ */
25
+ export declare function composeAgentBriefCore(context: AgentRunContext, registry: AgentKindRegistry, dispatch: AgentDispatchContext): AgentBriefCore;
26
+ /**
27
+ * The role prompt for one dispatch: the workspace's override (or the shipped track prompt), the
28
+ * block's resolved standards at this kind's verbosity, and its trait guidance.
29
+ *
30
+ * Its own exported function because two dispatch paths must produce the identical string from one
31
+ * context. It is the piece a workspace actually EDITS, so a drift here is the failure that matters
32
+ * most: the standards a team agreed on reaching one executor and not the other, with nothing
33
+ * anywhere reporting it.
34
+ */
35
+ export declare function composeRoleSystemPrompt(context: AgentRunContext, registry: AgentKindRegistry): string;
36
+ /** The repo + branch facts a delegated dispatch resolved, as the brief names them. */
37
+ export interface DelegationBriefTarget {
38
+ repo: DelegationBrief['repo'];
39
+ branches: DelegationBrief['branches'];
40
+ /** Where the work came from, when it came from a tracker. */
41
+ trackerRef?: NonNullable<DelegationBrief['task']['trackerRef']>;
42
+ }
43
+ /**
44
+ * Build the {@link DelegationBrief} for one delegated dispatch.
45
+ *
46
+ * `ownService` is FILLED IN rather than forwarded optionally: the brief's contract is a
47
+ * discriminated result, and an omitted one reads to a model exactly like a task whose product is
48
+ * obvious. A context that carries none genuinely does not know which system the work belongs to,
49
+ * and `not-under-a-service` is what says so.
50
+ */
51
+ export declare function composeDelegationBrief(context: AgentRunContext, registry: AgentKindRegistry, args: {
52
+ correlationKey: string;
53
+ workspaceId: string;
54
+ blockId: string;
55
+ runId: string;
56
+ stepIndex: number;
57
+ target: DelegationBriefTarget;
58
+ }): DelegationBrief;
59
+ /** Narrow a repo projection's stored provider to the union, defaulting to the pre-column value. */
60
+ export declare function briefRepoProvider(provider: VcsProvider | undefined): VcsProvider;
61
+ //# sourceMappingURL=brief.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brief.d.ts","sourceRoot":"","sources":["../../src/agents/brief.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EACpB,eAAe,EACf,eAAe,EACf,mBAAmB,EAEnB,WAAW,EACZ,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACL,KAAK,iBAAiB,EAMvB,MAAM,qBAAqB,CAAA;AAqB5B,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,gBAAgB,EAAE,MAAM,CAAA;IACxB,+FAA+F;IAC/F,UAAU,EAAE,MAAM,CAAA;IAClB,iEAAiE;IACjE,YAAY,EAAE,mBAAmB,EAAE,CAAA;CACpC;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,iBAAiB,EAC3B,QAAQ,EAAE,oBAAoB,GAC7B,cAAc,CAYhB;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,iBAAiB,GAC1B,MAAM,CAQR;AAED,sFAAsF;AACtF,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,CAAA;IAC7B,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,CAAA;IACrC,6DAA6D;IAC7D,UAAU,CAAC,EAAE,WAAW,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAA;CAChE;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,iBAAiB,EAC3B,IAAI,EAAE;IACJ,cAAc,EAAE,MAAM,CAAA;IACtB,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,qBAAqB,CAAA;CAC9B,GACA,eAAe,CAyCjB;AAOD,mGAAmG;AACnG,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,WAAW,GAAG,SAAS,GAAG,WAAW,CAEhF"}