@cat-factory/server 0.325.2 → 0.326.1
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/agents/CompositeAgentExecutor.d.ts +22 -3
- package/dist/agents/CompositeAgentExecutor.d.ts.map +1 -1
- package/dist/agents/CompositeAgentExecutor.js +91 -5
- package/dist/agents/CompositeAgentExecutor.js.map +1 -1
- package/dist/agents/DelegatedAgentExecutor.d.ts +147 -0
- package/dist/agents/DelegatedAgentExecutor.d.ts.map +1 -0
- package/dist/agents/DelegatedAgentExecutor.js +537 -0
- package/dist/agents/DelegatedAgentExecutor.js.map +1 -0
- package/dist/agents/brief.d.ts +61 -0
- package/dist/agents/brief.d.ts.map +1 -0
- package/dist/agents/brief.js +94 -0
- package/dist/agents/brief.js.map +1 -0
- package/dist/agents/containerJobAddressing.d.ts +1 -20
- package/dist/agents/containerJobAddressing.d.ts.map +1 -1
- package/dist/agents/containerJobAddressing.js +5 -23
- package/dist/agents/containerJobAddressing.js.map +1 -1
- package/dist/agents/delegatedExecutorHost.d.ts +90 -0
- package/dist/agents/delegatedExecutorHost.d.ts.map +1 -0
- package/dist/agents/delegatedExecutorHost.js +124 -0
- package/dist/agents/delegatedExecutorHost.js.map +1 -0
- package/dist/agents/delegationCredentials.d.ts +20 -0
- package/dist/agents/delegationCredentials.d.ts.map +1 -0
- package/dist/agents/delegationCredentials.js +63 -0
- package/dist/agents/delegationCredentials.js.map +1 -0
- package/dist/agents/delegationWorkBranch.d.ts +31 -0
- package/dist/agents/delegationWorkBranch.d.ts.map +1 -0
- package/dist/agents/delegationWorkBranch.js +103 -0
- package/dist/agents/delegationWorkBranch.js.map +1 -0
- package/dist/agents/jobBody.d.ts.map +1 -1
- package/dist/agents/jobBody.js +8 -9
- package/dist/agents/jobBody.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/publicApi/openapiDocument.generated.d.ts +1 -1
- package/dist/modules/publicApi/openapiDocument.generated.d.ts.map +1 -1
- package/dist/modules/publicApi/openapiDocument.generated.js +1 -1
- package/dist/modules/publicApi/openapiDocument.generated.js.map +1 -1
- package/dist/modules/workspaces/WorkspaceController.d.ts.map +1 -1
- package/dist/modules/workspaces/WorkspaceController.js +42 -1
- package/dist/modules/workspaces/WorkspaceController.js.map +1 -1
- package/dist/persistence/agentKinds.d.ts +6 -0
- package/dist/persistence/agentKinds.d.ts.map +1 -1
- package/dist/persistence/agentKinds.js +6 -0
- package/dist/persistence/agentKinds.js.map +1 -1
- package/package.json +13 -13
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { bugFixGuidanceFor, composeBlockSystemPrompt, standardsDeliveredAsFiles, standardsVerbosityFor, userPromptFor, } from '@cat-factory/agents';
|
|
2
|
+
import { dispatchSystemPromptFor } from './promptOverrides.js';
|
|
3
|
+
/**
|
|
4
|
+
* Compose the executor-independent half of a dispatch's instructions.
|
|
5
|
+
*
|
|
6
|
+
* `dispatch` carries the resolved checkout facts for the kinds whose own prompt names a branch.
|
|
7
|
+
* Both callers have one (a delegated executor gets a checkout too: its own), which is the
|
|
8
|
+
* difference between this and an inline caller, and it is why the parameter is required here
|
|
9
|
+
* rather than optional as it is on `userPromptFor`.
|
|
10
|
+
*/
|
|
11
|
+
export function composeAgentBriefCore(context, registry, dispatch) {
|
|
12
|
+
return {
|
|
13
|
+
roleSystemPrompt: composeRoleSystemPrompt(context, registry),
|
|
14
|
+
// `materialized: true` on BOTH paths: linked context renders as an index pointing at the
|
|
15
|
+
// `.cat-context/` files rather than folding their bodies into the prompt, because the files
|
|
16
|
+
// themselves travel beside it. A delegated executor that cannot materialise them has to fold
|
|
17
|
+
// the bodies into its own input instead: the brief carries both halves so either is possible,
|
|
18
|
+
// and rendering the index for one caller and the bodies for the other would mean the two
|
|
19
|
+
// executors were briefed differently on the same task.
|
|
20
|
+
userPrompt: userPromptFor(context, registry, { materialized: true, dispatch }),
|
|
21
|
+
contextFiles: [...(context.injectedContextFiles ?? [])],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The role prompt for one dispatch: the workspace's override (or the shipped track prompt), the
|
|
26
|
+
* block's resolved standards at this kind's verbosity, and its trait guidance.
|
|
27
|
+
*
|
|
28
|
+
* Its own exported function because two dispatch paths must produce the identical string from one
|
|
29
|
+
* context. It is the piece a workspace actually EDITS, so a drift here is the failure that matters
|
|
30
|
+
* most: the standards a team agreed on reaching one executor and not the other, with nothing
|
|
31
|
+
* anywhere reporting it.
|
|
32
|
+
*/
|
|
33
|
+
export function composeRoleSystemPrompt(context, registry) {
|
|
34
|
+
return composeBlockSystemPrompt(dispatchSystemPromptFor(context, registry), context.block, registry.standardsDelivery(context.agentKind), standardsDeliveredAsFiles(context.injectedContextFiles), standardsVerbosityFor(context.agentKind, registry));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Build the {@link DelegationBrief} for one delegated dispatch.
|
|
38
|
+
*
|
|
39
|
+
* `ownService` is FILLED IN rather than forwarded optionally: the brief's contract is a
|
|
40
|
+
* discriminated result, and an omitted one reads to a model exactly like a task whose product is
|
|
41
|
+
* obvious. A context that carries none genuinely does not know which system the work belongs to,
|
|
42
|
+
* and `not-under-a-service` is what says so.
|
|
43
|
+
*/
|
|
44
|
+
export function composeDelegationBrief(context, registry, args) {
|
|
45
|
+
const dispatch = {
|
|
46
|
+
baseBranch: args.target.branches.base,
|
|
47
|
+
// The executor checks out the work branch: a delegated step is a producer, and the branch every
|
|
48
|
+
// step of this run's pipeline shares is the one its change has to land on.
|
|
49
|
+
checkoutBranch: args.target.branches.work,
|
|
50
|
+
workBranch: args.target.branches.work,
|
|
51
|
+
// One repo. A delegated executor is handed one repository and one branch pair; the multi-repo
|
|
52
|
+
// fan-out is a property of OUR harness's sibling checkouts, and claiming it here would have a
|
|
53
|
+
// kind's prompt describe checkouts the external system never makes.
|
|
54
|
+
multiRepo: false,
|
|
55
|
+
};
|
|
56
|
+
const core = composeAgentBriefCore(context, registry, dispatch);
|
|
57
|
+
// Bug-fix guidance rides the brief for the same reason it rides a container coding dispatch: it
|
|
58
|
+
// is about the WORK (fix the reported defect, do not merely satisfy the reproduction test), not
|
|
59
|
+
// about the machine. Empty for every kind and every run that no `repro-test` preceded.
|
|
60
|
+
const bugFix = bugFixGuidanceFor(context);
|
|
61
|
+
return {
|
|
62
|
+
correlationKey: args.correlationKey,
|
|
63
|
+
workspaceId: args.workspaceId,
|
|
64
|
+
// The same value `task.id` carries, stated twice because the two are different contracts: this
|
|
65
|
+
// one is the LOOKUP KEY `deps.repoFiles` takes beside the workspace, and `task` is the work's
|
|
66
|
+
// identity as an executor records it. An executor keying its repository read off the identity
|
|
67
|
+
// is one rename away from a resolver that answers nothing.
|
|
68
|
+
blockId: args.blockId,
|
|
69
|
+
runId: args.runId,
|
|
70
|
+
stepIndex: args.stepIndex,
|
|
71
|
+
agentKind: context.agentKind,
|
|
72
|
+
task: {
|
|
73
|
+
id: args.blockId,
|
|
74
|
+
title: context.block.title,
|
|
75
|
+
description: context.block.description,
|
|
76
|
+
...(args.target.trackerRef ? { trackerRef: args.target.trackerRef } : {}),
|
|
77
|
+
},
|
|
78
|
+
repo: args.target.repo,
|
|
79
|
+
branches: args.target.branches,
|
|
80
|
+
systemPrompt: bugFix ? `${core.roleSystemPrompt}\n\n${bugFix}` : core.roleSystemPrompt,
|
|
81
|
+
userPrompt: core.userPrompt,
|
|
82
|
+
contextFiles: core.contextFiles,
|
|
83
|
+
ownService: ownServiceFor(context),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** The discriminated own-service answer, defaulting to the one that SAYS the platform does not know. */
|
|
87
|
+
function ownServiceFor(context) {
|
|
88
|
+
return context.ownService ?? { stated: false, reason: 'not-under-a-service' };
|
|
89
|
+
}
|
|
90
|
+
/** Narrow a repo projection's stored provider to the union, defaulting to the pre-column value. */
|
|
91
|
+
export function briefRepoProvider(provider) {
|
|
92
|
+
return provider ?? 'github';
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=brief.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brief.js","sourceRoot":"","sources":["../../src/agents/brief.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,25 +1,6 @@
|
|
|
1
1
|
import type { AgentJobHandle, RunnerImageVariant, RunnerJobRef } from '@cat-factory/kernel';
|
|
2
2
|
import type { AgentKindRegistry } from '@cat-factory/agents';
|
|
3
|
-
|
|
4
|
-
* The harness job id for one dispatch: the run (execution) id, the agent kind, and — past the
|
|
5
|
-
* first job of that kind in the run — the `dispatchEpoch`. A run executes a sequence of steps that
|
|
6
|
-
* all share the one per-run container, so each job needs an id that is UNIQUE WITHIN THE RUN: the
|
|
7
|
-
* harness keys its per-kind job registries by it, and two jobs sharing an id alias there (the bug
|
|
8
|
-
* where an `architect` /explore poll read back the `spec-writer`'s /spec result). The run itself is
|
|
9
|
-
* addressed separately by the execution id (the {@link RunnerJobRef.runId}).
|
|
10
|
-
*
|
|
11
|
-
* The epoch is what makes that uniqueness total, because the engine dispatches one kind more than
|
|
12
|
-
* once per run in two ways: a step RE-dispatched (a Tester re-test after a fixer round, a gate's
|
|
13
|
-
* helper retry, a companion's rework round, an eviction recovery) and the same helper kind
|
|
14
|
-
* escalated off DIFFERENT steps (`fixer` serves four gates). The harness re-attaches to an EXISTING
|
|
15
|
-
* job id rather than re-running (replay idempotency), and a container-reusing transport — a warm
|
|
16
|
-
* local pool, a self-hosted runner pool — keeps that registry alive across rounds, since reclaiming
|
|
17
|
-
* a pooled member does NOT destroy it. So a reused id replays a completed job: the Tester that
|
|
18
|
-
* appeared to "pass regardless" and never re-tested, an eviction recovery landing back on the job
|
|
19
|
-
* whose runner just died. `dispatchEpochFor` counts the run's prior dispatches of the kind, so the
|
|
20
|
-
* id names the n-th job of that kind and the run's first keeps the unsuffixed shape.
|
|
21
|
-
*/
|
|
22
|
-
export declare function stepJobId(executionId: string, agentKind: string, dispatchEpoch?: number): string;
|
|
3
|
+
export { stepJobId } from '@cat-factory/kernel';
|
|
23
4
|
/** The provider slug from a handle's `provider:model` string (fallback when the handle omits `provider`). */
|
|
24
5
|
export declare function providerOf(model: string | undefined): string;
|
|
25
6
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"containerJobAddressing.d.ts","sourceRoot":"","sources":["../../src/agents/containerJobAddressing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAC3F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"containerJobAddressing.d.ts","sourceRoot":"","sources":["../../src/agents/containerJobAddressing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAC3F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAe5D,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAE/C,6GAA6G;AAC7G,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAI5D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,QAAQ,EAAE,iBAAiB,GAC1B,kBAAkB,GAAG,SAAS,CAEhC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,QAAQ,EAAE,iBAAiB,GAC1B,CAAC,kBAAkB,GAAG,SAAS,CAAC,EAAE,CAOpC;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,QAAQ,EAAE,iBAAiB,GAAG,YAAY,CAO9F"}
|
|
@@ -6,29 +6,11 @@
|
|
|
6
6
|
// context or the job handle, which is what lets the dispatch site and the poll site derive the
|
|
7
7
|
// same answer with nothing carried between them: the poll runs in another process after a
|
|
8
8
|
// durable replay and rebuilds the handle from the persisted step alone.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
* where an `architect` /explore poll read back the `spec-writer`'s /spec result). The run itself is
|
|
15
|
-
* addressed separately by the execution id (the {@link RunnerJobRef.runId}).
|
|
16
|
-
*
|
|
17
|
-
* The epoch is what makes that uniqueness total, because the engine dispatches one kind more than
|
|
18
|
-
* once per run in two ways: a step RE-dispatched (a Tester re-test after a fixer round, a gate's
|
|
19
|
-
* helper retry, a companion's rework round, an eviction recovery) and the same helper kind
|
|
20
|
-
* escalated off DIFFERENT steps (`fixer` serves four gates). The harness re-attaches to an EXISTING
|
|
21
|
-
* job id rather than re-running (replay idempotency), and a container-reusing transport — a warm
|
|
22
|
-
* local pool, a self-hosted runner pool — keeps that registry alive across rounds, since reclaiming
|
|
23
|
-
* a pooled member does NOT destroy it. So a reused id replays a completed job: the Tester that
|
|
24
|
-
* appeared to "pass regardless" and never re-tested, an eviction recovery landing back on the job
|
|
25
|
-
* whose runner just died. `dispatchEpochFor` counts the run's prior dispatches of the kind, so the
|
|
26
|
-
* id names the n-th job of that kind and the run's first keeps the unsuffixed shape.
|
|
27
|
-
*/
|
|
28
|
-
export function stepJobId(executionId, agentKind, dispatchEpoch = 0) {
|
|
29
|
-
const base = `${executionId}-${agentKind}`;
|
|
30
|
-
return dispatchEpoch > 0 ? `${base}-${dispatchEpoch}` : base;
|
|
31
|
-
}
|
|
9
|
+
// The per-dispatch job id itself lives in KERNEL (`stepJobId`), because the engine now mints the
|
|
10
|
+
// same string as a delegated step's correlation key and had to be able to commit it before any
|
|
11
|
+
// executor is called. Re-exported here so every existing importer keeps resolving it from the
|
|
12
|
+
// module that addresses container jobs.
|
|
13
|
+
export { stepJobId } from '@cat-factory/kernel';
|
|
32
14
|
/** The provider slug from a handle's `provider:model` string (fallback when the handle omits `provider`). */
|
|
33
15
|
export function providerOf(model) {
|
|
34
16
|
if (!model)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"containerJobAddressing.js","sourceRoot":"","sources":["../../src/agents/containerJobAddressing.ts"],"names":[],"mappings":"AAGA,8FAA8F;AAC9F,wFAAwF;AACxF,EAAE;AACF,gGAAgG;AAChG,+FAA+F;AAC/F,+FAA+F;AAC/F,0FAA0F;AAC1F,wEAAwE;AAExE
|
|
1
|
+
{"version":3,"file":"containerJobAddressing.js","sourceRoot":"","sources":["../../src/agents/containerJobAddressing.ts"],"names":[],"mappings":"AAGA,8FAA8F;AAC9F,wFAAwF;AACxF,EAAE;AACF,gGAAgG;AAChG,+FAA+F;AAC/F,+FAA+F;AAC/F,0FAA0F;AAC1F,wEAAwE;AAExE,iGAAiG;AACjG,+FAA+F;AAC/F,8FAA8F;AAC9F,wCAAwC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAE/C,6GAA6G;AAC7G,MAAM,UAAU,UAAU,CAAC,KAAyB;IAClD,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAA;IAC5B,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAChC,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;AAClD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAC7B,SAA6B,EAC7B,QAA2B;IAE3B,OAAO,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;AACrE,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB,CAC9B,UAA6B,EAC7B,QAA2B;IAE3B,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAiC,CAAC,SAAS,CAAC,CAAC,CAAA;IACrE,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;QAC7C,IAAI,KAAK,IAAI,KAAK,KAAK,SAAS;YAAE,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;IACvD,CAAC;IACD,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAA;AACtB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,MAAsB,EAAE,QAA2B;IAC9E,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAA;IACzD,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK;QACnC,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5B,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { AgentContextRecorder, Clock, DelegatedExecutorRegistry, DelegatedFetch, Logger, ResolveRunRepoContext, TaskRepository, ToolSecretResolver, UrlSafetyPolicy } from '@cat-factory/kernel';
|
|
2
|
+
import type { AgentKindRegistry } from '@cat-factory/agents';
|
|
3
|
+
import { DelegatedAgentExecutor } from './DelegatedAgentExecutor.js';
|
|
4
|
+
import type { ResolveRepoOrigin, ResolveRepoTarget } from './repoTargeting.js';
|
|
5
|
+
/** What a facade supplies to stand the delegated arm up. */
|
|
6
|
+
export interface DelegatedExecutorHostOptions {
|
|
7
|
+
delegatedExecutorRegistry: DelegatedExecutorRegistry;
|
|
8
|
+
agentKindRegistry: AgentKindRegistry;
|
|
9
|
+
resolveRepoTarget: ResolveRepoTarget;
|
|
10
|
+
/**
|
|
11
|
+
* Where a repo is reached: the clone URL plus the VCS provider.
|
|
12
|
+
*
|
|
13
|
+
* REQUIRED, for the reason `urlSafetyPolicy` below is: optional, the Worker facade simply did
|
|
14
|
+
* not pass it, and every delegated brief on that facade named a `https://github.com/...` clone
|
|
15
|
+
* URL, including for repositories on the deployment's own GitLab. A facade with nothing to
|
|
16
|
+
* resolve passes `githubRepoOrigin` by name.
|
|
17
|
+
*/
|
|
18
|
+
resolveRepoOrigin: ResolveRepoOrigin;
|
|
19
|
+
/**
|
|
20
|
+
* The engine's checkout-free repo binding, re-used as the `repoFiles` an executor may stage its
|
|
21
|
+
* own context layer through, and as what the engine creates a `platform-creates` work branch
|
|
22
|
+
* with. The SAME seam a registered kind's pre/post-ops run over, rather than a second binding:
|
|
23
|
+
* an executor writing `.cat-context/` onto the work branch and a preOp writing it are the same
|
|
24
|
+
* operation, and two bindings would be two caches and two head memos.
|
|
25
|
+
*
|
|
26
|
+
* Absent ⇒ this deployment configured no VCS provider. NOT asserted here even though a
|
|
27
|
+
* `platform-creates` registration needs it: this function runs inside the Worker's per-request
|
|
28
|
+
* container build, so a throw turns a delegated-only misconfiguration into a 500 on every
|
|
29
|
+
* unrelated endpoint, including the settings the operator would go and look at. The dispatch
|
|
30
|
+
* that actually needs the branch refuses instead, by name and with translated copy
|
|
31
|
+
* (`delegated_work_branch_unprepared`).
|
|
32
|
+
*/
|
|
33
|
+
resolveRunRepoContext?: ResolveRunRepoContext;
|
|
34
|
+
taskRepository?: TaskRepository;
|
|
35
|
+
resolveToolSecrets?: ToolSecretResolver;
|
|
36
|
+
agentContextObservability?: AgentContextRecorder;
|
|
37
|
+
/**
|
|
38
|
+
* The deployment's outbound-URL policy. The SAME one the notification-webhook sender is held to,
|
|
39
|
+
* because an executor is an outbound HTTP surface the deployment configured and a second set of
|
|
40
|
+
* SSRF rules is a set nobody maintains.
|
|
41
|
+
*
|
|
42
|
+
* REQUIRED, though its value may be `undefined` (which means the strict public-https default).
|
|
43
|
+
* Optional, it was declared here, declared on the kernel port, documented on both, and passed by
|
|
44
|
+
* neither facade: every registered executor was built with an SSRF control that existed only in
|
|
45
|
+
* the types. A required field makes forgetting it a typecheck failure instead.
|
|
46
|
+
*
|
|
47
|
+
* It is ENFORCED here rather than handed to each executor, by wrapping the fetch they are all
|
|
48
|
+
* built over (see {@link policyCheckedFetch}). An executor is deployment-authored code, and a
|
|
49
|
+
* control every author has to remember to apply is the same control that existed only in the
|
|
50
|
+
* types.
|
|
51
|
+
*/
|
|
52
|
+
urlSafetyPolicy: UrlSafetyPolicy | undefined;
|
|
53
|
+
/** The runtime's fetch. Defaults to the global one, which both runtimes provide. */
|
|
54
|
+
fetchImpl?: DelegatedFetch;
|
|
55
|
+
logger: Logger;
|
|
56
|
+
clock: Clock;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Build the delegated arm for the composite executor.
|
|
60
|
+
*
|
|
61
|
+
* Built UNCONDITIONALLY, even when the registry is empty. The alternative (a null arm when
|
|
62
|
+
* nothing is registered) reads as an optimisation and is a trap: a MOTHERSHIP-MODE node resolves
|
|
63
|
+
* its agent kinds from the mothership and its own registry can be a build behind, so "this process
|
|
64
|
+
* registers none" is not the same fact as "this run has no delegated step". With the arm always
|
|
65
|
+
* present, such a step is refused by name (`delegated_executor_unwired`, naming what IS
|
|
66
|
+
* registered) instead of a bare "no delegated executor wired" from the composite.
|
|
67
|
+
*/
|
|
68
|
+
export declare function buildDelegatedAgentExecutor(options: DelegatedExecutorHostOptions): DelegatedAgentExecutor;
|
|
69
|
+
/**
|
|
70
|
+
* How long one executor call may take, redirects included, before it is abandoned.
|
|
71
|
+
*
|
|
72
|
+
* A delegated executor's own calls are API requests (dispatch, poll, cancel), not the external
|
|
73
|
+
* WORK, whose hours are bounded by the poll policy instead. Without a deadline a hung endpoint
|
|
74
|
+
* holds `DelegatedAgentExecutor.pollJob` open for ever, which on Node ties up a pg-boss worker and
|
|
75
|
+
* on the Worker burns the invocation: the ordinary shape of an outage, and the reason the
|
|
76
|
+
* notification-webhook sender sets one against this same `safeFetch`. A caller that passes its own
|
|
77
|
+
* `signal` keeps it.
|
|
78
|
+
*/
|
|
79
|
+
export declare const DELEGATED_REQUEST_TIMEOUT_MS = 30000;
|
|
80
|
+
/**
|
|
81
|
+
* How much of one response body an executor may read.
|
|
82
|
+
*
|
|
83
|
+
* The third protection `safe-fetch` exists for, and the one a wrapper that only re-validates hops
|
|
84
|
+
* leaves off. An executor is deployment-authored code reading a system's JSON, and a broken or
|
|
85
|
+
* hostile endpoint answering hundreds of megabytes would otherwise be buffered whole in the
|
|
86
|
+
* isolate. Generous against the real payloads (a page of Actions runs is a few hundred KB) and
|
|
87
|
+
* fatal well before an OOM.
|
|
88
|
+
*/
|
|
89
|
+
export declare const DELEGATED_RESPONSE_MAX_BYTES: number;
|
|
90
|
+
//# sourceMappingURL=delegatedExecutorHost.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delegatedExecutorHost.d.ts","sourceRoot":"","sources":["../../src/agents/delegatedExecutorHost.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EACpB,KAAK,EAEL,yBAAyB,EACzB,cAAc,EAEd,MAAM,EACN,qBAAqB,EACrB,cAAc,EACd,kBAAkB,EAClB,eAAe,EAChB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAQ5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAW9E,4DAA4D;AAC5D,MAAM,WAAW,4BAA4B;IAC3C,yBAAyB,EAAE,yBAAyB,CAAA;IACpD,iBAAiB,EAAE,iBAAiB,CAAA;IACpC,iBAAiB,EAAE,iBAAiB,CAAA;IACpC;;;;;;;OAOG;IACH,iBAAiB,EAAE,iBAAiB,CAAA;IACpC;;;;;;;;;;;;;OAaG;IACH,qBAAqB,CAAC,EAAE,qBAAqB,CAAA;IAC7C,cAAc,CAAC,EAAE,cAAc,CAAA;IAC/B,kBAAkB,CAAC,EAAE,kBAAkB,CAAA;IACvC,yBAAyB,CAAC,EAAE,oBAAoB,CAAA;IAChD;;;;;;;;;;;;;;OAcG;IACH,eAAe,EAAE,eAAe,GAAG,SAAS,CAAA;IAC5C,oFAAoF;IACpF,SAAS,CAAC,EAAE,cAAc,CAAA;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,KAAK,CAAA;CACb;AAED;;;;;;;;;GASG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,4BAA4B,GACpC,sBAAsB,CA4BxB;AAYD;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B,QAAS,CAAA;AAElD;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B,QAAkB,CAAA"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { UnavailableError } from '@cat-factory/kernel';
|
|
2
|
+
import { assertSafePublicUrl, readCappedText, safeFetch, DEFAULT_MAX_REDIRECTS, } from '@cat-factory/integrations';
|
|
3
|
+
import { DelegatedAgentExecutor } from './DelegatedAgentExecutor.js';
|
|
4
|
+
/**
|
|
5
|
+
* Build the delegated arm for the composite executor.
|
|
6
|
+
*
|
|
7
|
+
* Built UNCONDITIONALLY, even when the registry is empty. The alternative (a null arm when
|
|
8
|
+
* nothing is registered) reads as an optimisation and is a trap: a MOTHERSHIP-MODE node resolves
|
|
9
|
+
* its agent kinds from the mothership and its own registry can be a build behind, so "this process
|
|
10
|
+
* registers none" is not the same fact as "this run has no delegated step". With the arm always
|
|
11
|
+
* present, such a step is refused by name (`delegated_executor_unwired`, naming what IS
|
|
12
|
+
* registered) instead of a bare "no delegated executor wired" from the composite.
|
|
13
|
+
*/
|
|
14
|
+
export function buildDelegatedAgentExecutor(options) {
|
|
15
|
+
const resolveRepoFiles = options.resolveRunRepoContext
|
|
16
|
+
? repoFilesResolver(options.resolveRunRepoContext)
|
|
17
|
+
: undefined;
|
|
18
|
+
const executorDeps = {
|
|
19
|
+
logger: options.logger.child({ component: 'delegatedExecutor' }),
|
|
20
|
+
clock: options.clock,
|
|
21
|
+
fetchImpl: policyCheckedFetch(options.fetchImpl ?? globalThis.fetch, options.urlSafetyPolicy),
|
|
22
|
+
...(resolveRepoFiles ? { repoFiles: resolveRepoFiles } : {}),
|
|
23
|
+
};
|
|
24
|
+
return new DelegatedAgentExecutor({
|
|
25
|
+
...(resolveRepoFiles ? { resolveRepoFiles } : {}),
|
|
26
|
+
delegatedExecutorRegistry: options.delegatedExecutorRegistry,
|
|
27
|
+
agentKindRegistry: options.agentKindRegistry,
|
|
28
|
+
resolveRepoTarget: options.resolveRepoTarget,
|
|
29
|
+
resolveRepoOrigin: options.resolveRepoOrigin,
|
|
30
|
+
...(options.taskRepository ? { taskRepository: options.taskRepository } : {}),
|
|
31
|
+
...(options.resolveToolSecrets ? { resolveToolSecrets: options.resolveToolSecrets } : {}),
|
|
32
|
+
...(options.agentContextObservability
|
|
33
|
+
? { agentContextObservability: options.agentContextObservability }
|
|
34
|
+
: {}),
|
|
35
|
+
executorDeps,
|
|
36
|
+
logger: options.logger,
|
|
37
|
+
clock: options.clock,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/** Project the engine's run-repo binding down to the narrower shape an executor is handed. */
|
|
41
|
+
function repoFilesResolver(resolveRunRepoContext) {
|
|
42
|
+
return async ({ workspaceId, blockId }) => {
|
|
43
|
+
const bound = await resolveRunRepoContext(workspaceId, blockId);
|
|
44
|
+
return bound?.repo ?? null;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* How long one executor call may take, redirects included, before it is abandoned.
|
|
49
|
+
*
|
|
50
|
+
* A delegated executor's own calls are API requests (dispatch, poll, cancel), not the external
|
|
51
|
+
* WORK, whose hours are bounded by the poll policy instead. Without a deadline a hung endpoint
|
|
52
|
+
* holds `DelegatedAgentExecutor.pollJob` open for ever, which on Node ties up a pg-boss worker and
|
|
53
|
+
* on the Worker burns the invocation: the ordinary shape of an outage, and the reason the
|
|
54
|
+
* notification-webhook sender sets one against this same `safeFetch`. A caller that passes its own
|
|
55
|
+
* `signal` keeps it.
|
|
56
|
+
*/
|
|
57
|
+
export const DELEGATED_REQUEST_TIMEOUT_MS = 30_000;
|
|
58
|
+
/**
|
|
59
|
+
* How much of one response body an executor may read.
|
|
60
|
+
*
|
|
61
|
+
* The third protection `safe-fetch` exists for, and the one a wrapper that only re-validates hops
|
|
62
|
+
* leaves off. An executor is deployment-authored code reading a system's JSON, and a broken or
|
|
63
|
+
* hostile endpoint answering hundreds of megabytes would otherwise be buffered whole in the
|
|
64
|
+
* isolate. Generous against the real payloads (a page of Actions runs is a few hundred KB) and
|
|
65
|
+
* fatal well before an OOM.
|
|
66
|
+
*/
|
|
67
|
+
export const DELEGATED_RESPONSE_MAX_BYTES = 4 * 1024 * 1024;
|
|
68
|
+
/**
|
|
69
|
+
* The fetch every registered executor is built over: the runtime's own, held to the deployment's
|
|
70
|
+
* outbound-URL policy on the first URL AND on every redirect hop, given a deadline, and capped on
|
|
71
|
+
* the way back.
|
|
72
|
+
*
|
|
73
|
+
* The SAME guard the notification-webhook sender uses, through the same `safeFetch`, because an
|
|
74
|
+
* executor is the same kind of surface: an operator-supplied base URL, a credential-bearing
|
|
75
|
+
* request, and a receiver free to answer 302. Re-validating each hop is the part a plain
|
|
76
|
+
* scheme check at registration cannot do, and `safeFetch` additionally strips the body and the
|
|
77
|
+
* credential headers when a hop crosses origins.
|
|
78
|
+
*
|
|
79
|
+
* All THREE of that module's protections are applied here rather than two, and for the reason the
|
|
80
|
+
* policy itself is enforced here: an executor is deployment-authored code, and a control every
|
|
81
|
+
* author has to remember to apply is a control that exists only in the types.
|
|
82
|
+
*
|
|
83
|
+
* A refused URL throws `ValidationError` out of the executor's own call, which its error path
|
|
84
|
+
* reports like any other refusal from its system.
|
|
85
|
+
*/
|
|
86
|
+
function policyCheckedFetch(fetchImpl, policy) {
|
|
87
|
+
const assertSafe = (url) => assertSafePublicUrl(url, {
|
|
88
|
+
subject: 'Delegated executor',
|
|
89
|
+
label: 'endpoint',
|
|
90
|
+
...(policy ? { policy } : {}),
|
|
91
|
+
});
|
|
92
|
+
const makeError = (status, message) => new UnavailableError(`A delegated executor's request could not be completed: ${message}`, 'delegated_executor_failed', { status });
|
|
93
|
+
return async (url, init) => {
|
|
94
|
+
const response = await safeFetch(url, {
|
|
95
|
+
...(init ?? {}),
|
|
96
|
+
// The caller's own deadline wins where it set one; otherwise the deployment's. The port
|
|
97
|
+
// declares `signal` as `unknown` (kernel compiles against no runtime's globals), so the
|
|
98
|
+
// narrowing happens here, where a real `RequestInit` is being built.
|
|
99
|
+
signal: init?.signal ??
|
|
100
|
+
AbortSignal.timeout(DELEGATED_REQUEST_TIMEOUT_MS),
|
|
101
|
+
}, assertSafe, makeError, DEFAULT_MAX_REDIRECTS, fetchImpl);
|
|
102
|
+
return cappedResponse(response, makeError);
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The response an executor sees: the real one's status and headers, with the two body readers
|
|
107
|
+
* routed through the running byte cap.
|
|
108
|
+
*
|
|
109
|
+
* A wrapper rather than a rule each executor applies, because the port hands them only `text()`
|
|
110
|
+
* and `json()` and neither can be bounded from the outside. The cap THROWS rather than truncating,
|
|
111
|
+
* which is right on this path: a body that overran is not a smaller body, and a JSON reader handed
|
|
112
|
+
* a prefix would parse a fault as a malformed payload.
|
|
113
|
+
*/
|
|
114
|
+
function cappedResponse(response, makeError) {
|
|
115
|
+
const read = () => readCappedText(response, DELEGATED_RESPONSE_MAX_BYTES, makeError);
|
|
116
|
+
return {
|
|
117
|
+
ok: response.ok,
|
|
118
|
+
status: response.status,
|
|
119
|
+
headers: response.headers,
|
|
120
|
+
text: read,
|
|
121
|
+
json: async () => JSON.parse(await read()),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=delegatedExecutorHost.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delegatedExecutorHost.js","sourceRoot":"","sources":["../../src/agents/delegatedExecutorHost.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { DelegatedExecutorDefinition, Logger, ToolSecretResolver } from '@cat-factory/kernel';
|
|
2
|
+
export interface ResolveDelegationCredentialsInput {
|
|
3
|
+
definition: DelegatedExecutorDefinition;
|
|
4
|
+
workspaceId: string;
|
|
5
|
+
/** The block, so a per-service credential store can scope its lookup. */
|
|
6
|
+
blockId?: string;
|
|
7
|
+
/** Facade-wired; absent ⇒ an empty bag, which the executor's own system reports as a refusal. */
|
|
8
|
+
resolveToolSecrets?: ToolSecretResolver;
|
|
9
|
+
logger?: Logger;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Resolve every credential a registered executor declared, keyed by the name the executor reads.
|
|
13
|
+
*
|
|
14
|
+
* Never throws and never fails a dispatch. An unresolvable key simply is not in the bag, and the
|
|
15
|
+
* executor's own call then fails against its own system with that system's own message, which is
|
|
16
|
+
* strictly more useful than a platform-side refusal that names a key the operator has to go and
|
|
17
|
+
* map back to a vendor. The WARN below is what makes the gap visible either way.
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveDelegationCredentials(input: ResolveDelegationCredentialsInput): Promise<Record<string, string>>;
|
|
20
|
+
//# sourceMappingURL=delegationCredentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delegationCredentials.d.ts","sourceRoot":"","sources":["../../src/agents/delegationCredentials.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAiClG,MAAM,WAAW,iCAAiC;IAChD,UAAU,EAAE,2BAA2B,CAAA;IACvC,WAAW,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,iGAAiG;IACjG,kBAAkB,CAAC,EAAE,kBAAkB,CAAA;IACvC,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;GAOG;AACH,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,iCAAiC,GACvC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAsDjC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { noopLogger, runBestEffort } from '@cat-factory/kernel';
|
|
2
|
+
import { credentialInjectionName, isReservedPlatformEnvKey, reservedEnvKeyMessage, } from '@cat-factory/contracts';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve every credential a registered executor declared, keyed by the name the executor reads.
|
|
5
|
+
*
|
|
6
|
+
* Never throws and never fails a dispatch. An unresolvable key simply is not in the bag, and the
|
|
7
|
+
* executor's own call then fails against its own system with that system's own message, which is
|
|
8
|
+
* strictly more useful than a platform-side refusal that names a key the operator has to go and
|
|
9
|
+
* map back to a vendor. The WARN below is what makes the gap visible either way.
|
|
10
|
+
*/
|
|
11
|
+
export async function resolveDelegationCredentials(input) {
|
|
12
|
+
const declared = input.definition.credentials ?? [];
|
|
13
|
+
const resolver = input.resolveToolSecrets;
|
|
14
|
+
if (declared.length === 0 || !resolver)
|
|
15
|
+
return {};
|
|
16
|
+
const log = input.logger ?? noopLogger;
|
|
17
|
+
const admissible = declared.filter((credential) => {
|
|
18
|
+
if (!isReservedPlatformEnvKey(credential.key))
|
|
19
|
+
return true;
|
|
20
|
+
// WARN rather than the `debug` an optional missing key gets: this is never a deployment's
|
|
21
|
+
// stated normal, and its fix is a declaration rather than a variable to set.
|
|
22
|
+
log.warn('delegated executor declares a reserved credential key; withholding it', {
|
|
23
|
+
executor: input.definition.id,
|
|
24
|
+
credentialKey: credential.key,
|
|
25
|
+
detail: reservedEnvKeyMessage(credential.key),
|
|
26
|
+
});
|
|
27
|
+
return false;
|
|
28
|
+
});
|
|
29
|
+
if (admissible.length === 0)
|
|
30
|
+
return {};
|
|
31
|
+
const resolved = await runBestEffort(log, 'resolve delegated executor credentials', () => resolver.resolve({
|
|
32
|
+
workspaceId: input.workspaceId,
|
|
33
|
+
...(input.blockId ? { blockId: input.blockId } : {}),
|
|
34
|
+
subject: { kind: 'delegated-executor', id: input.definition.id },
|
|
35
|
+
// Distinct LOOKUP keys: one stored value delivered under two names is an allowed
|
|
36
|
+
// declaration, and asking for the same key twice in one call is not.
|
|
37
|
+
keys: [...new Set(admissible.map((credential) => credential.key))].map((key) => ({ key })),
|
|
38
|
+
}), { executor: input.definition.id });
|
|
39
|
+
const bag = {};
|
|
40
|
+
for (const credential of admissible) {
|
|
41
|
+
const value = resolved?.[credential.key];
|
|
42
|
+
// The bag is keyed by the name the EXECUTOR reads (`envName` when it declared one, else the
|
|
43
|
+
// lookup key), the same fallback every other capability applies, so a declaration that renames
|
|
44
|
+
// a key for a vendor's SDK reads the same here as it does in a container.
|
|
45
|
+
if (value) {
|
|
46
|
+
bag[credentialInjectionName(credential)] = value;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (credential.required === false) {
|
|
50
|
+
log.debug('optional delegated credential did not resolve; calling without it', {
|
|
51
|
+
executor: input.definition.id,
|
|
52
|
+
credentialKey: credential.key,
|
|
53
|
+
});
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
log.warn('delegated credential did not resolve; the executor is called without it', {
|
|
57
|
+
executor: input.definition.id,
|
|
58
|
+
credentialKey: credential.key,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return bag;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=delegationCredentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delegationCredentials.js","sourceRoot":"","sources":["../../src/agents/delegationCredentials.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type DelegatedRepoFilesResolver, type DelegationBrief, type Logger } from '@cat-factory/kernel';
|
|
2
|
+
/** What {@link ensureDelegatedWorkBranch} needs, as bound callbacks rather than a container. */
|
|
3
|
+
export interface DelegationWorkBranchDeps {
|
|
4
|
+
/** Absent ⇒ this deployment configured no VCS provider, which is its own refusal below. */
|
|
5
|
+
resolveRepoFiles: DelegatedRepoFilesResolver | undefined;
|
|
6
|
+
logger: Logger;
|
|
7
|
+
}
|
|
8
|
+
/** One request to prepare a branch: whose dispatch it is for, and who may create it. */
|
|
9
|
+
export interface DelegationWorkBranchRequest {
|
|
10
|
+
executorId: string;
|
|
11
|
+
/**
|
|
12
|
+
* Whether the task named this branch itself (an apriori WORKING branch) rather than the engine
|
|
13
|
+
* deriving `cat-factory/<blockId>`. The platform never CREATES one of those: a run whose user
|
|
14
|
+
* picked an existing feature branch and found an empty one instead cannot tell that from the
|
|
15
|
+
* run ignoring their choice. Probed and refused when absent, exactly as the container path
|
|
16
|
+
* refuses it (`ContainerAgentExecutor.resolveWorkBranchReady`).
|
|
17
|
+
*/
|
|
18
|
+
apriori: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Make sure the brief's work branch exists before the executor is called, creating it at the base
|
|
22
|
+
* branch's head when the platform is the one allowed to.
|
|
23
|
+
*
|
|
24
|
+
* IDEMPOTENT by construction, which both durable drivers require: a replayed dispatch re-reads the
|
|
25
|
+
* head and finds the ref it made last time. The concurrent case is settled by CONTENT rather than
|
|
26
|
+
* by parsing a provider's refusal, because the two providers answer a lost create differently (a
|
|
27
|
+
* GitHub 422, a GitLab 400) and a failing create is indistinguishable from a losing one by status
|
|
28
|
+
* alone: re-read the ref, and let the write's own error propagate when it genuinely is not there.
|
|
29
|
+
*/
|
|
30
|
+
export declare function ensureDelegatedWorkBranch(deps: DelegationWorkBranchDeps, brief: DelegationBrief, request: DelegationWorkBranchRequest): Promise<void>;
|
|
31
|
+
//# sourceMappingURL=delegationWorkBranch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delegationWorkBranch.d.ts","sourceRoot":"","sources":["../../src/agents/delegationWorkBranch.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,0BAA0B,EAC/B,KAAK,eAAe,EACpB,KAAK,MAAM,EAEZ,MAAM,qBAAqB,CAAA;AAyB5B,gGAAgG;AAChG,MAAM,WAAW,wBAAwB;IACvC,2FAA2F;IAC3F,gBAAgB,EAAE,0BAA0B,GAAG,SAAS,CAAA;IACxD,MAAM,EAAE,MAAM,CAAA;CACf;AAED,wFAAwF;AACxF,MAAM,WAAW,2BAA2B;IAC1C,UAAU,EAAE,MAAM,CAAA;IAClB;;;;;;OAMG;IACH,OAAO,EAAE,OAAO,CAAA;CACjB;AAED;;;;;;;;;GASG;AACH,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,wBAAwB,EAC9B,KAAK,EAAE,eAAe,EACtB,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,IAAI,CAAC,CA8Cf"}
|