@alpacakit/agents 0.1.0-beta.19

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 (249) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +224 -0
  3. package/dist/agents/builtin.d.ts +19 -0
  4. package/dist/agents/builtin.d.ts.map +1 -0
  5. package/dist/agents/builtin.js +14 -0
  6. package/dist/agents/claude.d.ts +20 -0
  7. package/dist/agents/claude.d.ts.map +1 -0
  8. package/dist/agents/claude.js +480 -0
  9. package/dist/agents/codex.d.ts +19 -0
  10. package/dist/agents/codex.d.ts.map +1 -0
  11. package/dist/agents/codex.js +355 -0
  12. package/dist/agents/openai-compatible/config.d.ts +16 -0
  13. package/dist/agents/openai-compatible/config.d.ts.map +1 -0
  14. package/dist/agents/openai-compatible/config.js +160 -0
  15. package/dist/agents/openai-compatible/engine.d.ts +18 -0
  16. package/dist/agents/openai-compatible/engine.d.ts.map +1 -0
  17. package/dist/agents/openai-compatible/engine.js +10 -0
  18. package/dist/agents/openai-compatible/fetch-engine.d.ts +38 -0
  19. package/dist/agents/openai-compatible/fetch-engine.d.ts.map +1 -0
  20. package/dist/agents/openai-compatible/fetch-engine.js +216 -0
  21. package/dist/agents/openai-compatible/pi/engine.d.ts +32 -0
  22. package/dist/agents/openai-compatible/pi/engine.d.ts.map +1 -0
  23. package/dist/agents/openai-compatible/pi/engine.js +98 -0
  24. package/dist/agents/openai-compatible/pi/module.d.ts +87 -0
  25. package/dist/agents/openai-compatible/pi/module.d.ts.map +1 -0
  26. package/dist/agents/openai-compatible/pi/module.js +43 -0
  27. package/dist/agents/openai-compatible/pi/sandbox.d.ts +25 -0
  28. package/dist/agents/openai-compatible/pi/sandbox.d.ts.map +1 -0
  29. package/dist/agents/openai-compatible/pi/sandbox.js +69 -0
  30. package/dist/agents/openai-compatible/pi/session.d.ts +17 -0
  31. package/dist/agents/openai-compatible/pi/session.d.ts.map +1 -0
  32. package/dist/agents/openai-compatible/pi/session.js +354 -0
  33. package/dist/agents/openai-compatible/response.d.ts +19 -0
  34. package/dist/agents/openai-compatible/response.d.ts.map +1 -0
  35. package/dist/agents/openai-compatible/response.js +203 -0
  36. package/dist/agents/openai-compatible/types.d.ts +34 -0
  37. package/dist/agents/openai-compatible/types.d.ts.map +1 -0
  38. package/dist/agents/openai-compatible/types.js +1 -0
  39. package/dist/agents/openai-compatible.d.ts +12 -0
  40. package/dist/agents/openai-compatible.d.ts.map +1 -0
  41. package/dist/agents/openai-compatible.js +45 -0
  42. package/dist/auth/login-probe.d.ts +21 -0
  43. package/dist/auth/login-probe.d.ts.map +1 -0
  44. package/dist/auth/login-probe.js +47 -0
  45. package/dist/auth/login.d.ts +79 -0
  46. package/dist/auth/login.d.ts.map +1 -0
  47. package/dist/auth/login.js +9 -0
  48. package/dist/auth/material.d.ts +87 -0
  49. package/dist/auth/material.d.ts.map +1 -0
  50. package/dist/auth/material.js +59 -0
  51. package/dist/auth/profile-store.d.ts +14 -0
  52. package/dist/auth/profile-store.d.ts.map +1 -0
  53. package/dist/auth/profile-store.js +101 -0
  54. package/dist/auth/secrets.d.ts +14 -0
  55. package/dist/auth/secrets.d.ts.map +1 -0
  56. package/dist/auth/secrets.js +40 -0
  57. package/dist/codex/app-server.d.ts +30 -0
  58. package/dist/codex/app-server.d.ts.map +1 -0
  59. package/dist/codex/app-server.js +322 -0
  60. package/dist/codex/index.d.ts +3 -0
  61. package/dist/codex/index.d.ts.map +1 -0
  62. package/dist/codex/index.js +1 -0
  63. package/dist/codex/protocol.d.ts +124 -0
  64. package/dist/codex/protocol.d.ts.map +1 -0
  65. package/dist/codex/protocol.js +283 -0
  66. package/dist/config/agent-config-validation.d.ts +22 -0
  67. package/dist/config/agent-config-validation.d.ts.map +1 -0
  68. package/dist/config/agent-config-validation.js +31 -0
  69. package/dist/config/agent-configs.d.ts +37 -0
  70. package/dist/config/agent-configs.d.ts.map +1 -0
  71. package/dist/config/agent-configs.js +214 -0
  72. package/dist/config/compose.d.ts +10 -0
  73. package/dist/config/compose.d.ts.map +1 -0
  74. package/dist/config/compose.js +89 -0
  75. package/dist/config/config.d.ts +21 -0
  76. package/dist/config/config.d.ts.map +1 -0
  77. package/dist/config/config.js +33 -0
  78. package/dist/config/errors.d.ts +45 -0
  79. package/dist/config/errors.d.ts.map +1 -0
  80. package/dist/config/errors.js +37 -0
  81. package/dist/config/extension.d.ts +3 -0
  82. package/dist/config/extension.d.ts.map +1 -0
  83. package/dist/config/extension.js +1 -0
  84. package/dist/config/ids.d.ts +8 -0
  85. package/dist/config/ids.d.ts.map +1 -0
  86. package/dist/config/ids.js +4 -0
  87. package/dist/config/index.d.ts +12 -0
  88. package/dist/config/index.d.ts.map +1 -0
  89. package/dist/config/index.js +6 -0
  90. package/dist/config/model.d.ts +105 -0
  91. package/dist/config/model.d.ts.map +1 -0
  92. package/dist/config/model.js +1 -0
  93. package/dist/config/paths.d.ts +7 -0
  94. package/dist/config/paths.d.ts.map +1 -0
  95. package/dist/config/paths.js +10 -0
  96. package/dist/config/schema.d.ts +59 -0
  97. package/dist/config/schema.d.ts.map +1 -0
  98. package/dist/config/schema.js +83 -0
  99. package/dist/config/store.d.ts +34 -0
  100. package/dist/config/store.d.ts.map +1 -0
  101. package/dist/config/store.js +420 -0
  102. package/dist/config/support.d.ts +14 -0
  103. package/dist/config/support.d.ts.map +1 -0
  104. package/dist/config/support.js +32 -0
  105. package/dist/config/values.d.ts +21 -0
  106. package/dist/config/values.d.ts.map +1 -0
  107. package/dist/config/values.js +22 -0
  108. package/dist/config/xdg.d.ts +20 -0
  109. package/dist/config/xdg.d.ts.map +1 -0
  110. package/dist/config/xdg.js +23 -0
  111. package/dist/core/async-queue.d.ts +14 -0
  112. package/dist/core/async-queue.d.ts.map +1 -0
  113. package/dist/core/async-queue.js +46 -0
  114. package/dist/core/env.d.ts +19 -0
  115. package/dist/core/env.d.ts.map +1 -0
  116. package/dist/core/env.js +39 -0
  117. package/dist/core/error.d.ts +121 -0
  118. package/dist/core/error.d.ts.map +1 -0
  119. package/dist/core/error.js +88 -0
  120. package/dist/core/fs.d.ts +16 -0
  121. package/dist/core/fs.d.ts.map +1 -0
  122. package/dist/core/fs.js +30 -0
  123. package/dist/core/json.d.ts +20 -0
  124. package/dist/core/json.d.ts.map +1 -0
  125. package/dist/core/json.js +42 -0
  126. package/dist/core/node-ports.d.ts +9 -0
  127. package/dist/core/node-ports.d.ts.map +1 -0
  128. package/dist/core/node-ports.js +135 -0
  129. package/dist/core/ports.d.ts +71 -0
  130. package/dist/core/ports.d.ts.map +1 -0
  131. package/dist/core/ports.js +19 -0
  132. package/dist/core/redact.d.ts +8 -0
  133. package/dist/core/redact.d.ts.map +1 -0
  134. package/dist/core/redact.js +66 -0
  135. package/dist/core/run.d.ts +125 -0
  136. package/dist/core/run.d.ts.map +1 -0
  137. package/dist/core/run.js +27 -0
  138. package/dist/core/values.d.ts +12 -0
  139. package/dist/core/values.d.ts.map +1 -0
  140. package/dist/core/values.js +9 -0
  141. package/dist/corpus/failure-corpus.d.ts +21 -0
  142. package/dist/corpus/failure-corpus.d.ts.map +1 -0
  143. package/dist/corpus/failure-corpus.js +89 -0
  144. package/dist/fleet/fleet.d.ts +120 -0
  145. package/dist/fleet/fleet.d.ts.map +1 -0
  146. package/dist/fleet/fleet.js +409 -0
  147. package/dist/fleet/policy.d.ts +45 -0
  148. package/dist/fleet/policy.d.ts.map +1 -0
  149. package/dist/fleet/policy.js +50 -0
  150. package/dist/fleet/store.d.ts +28 -0
  151. package/dist/fleet/store.d.ts.map +1 -0
  152. package/dist/fleet/store.js +225 -0
  153. package/dist/host/capabilities.d.ts +146 -0
  154. package/dist/host/capabilities.d.ts.map +1 -0
  155. package/dist/host/capabilities.js +59 -0
  156. package/dist/host/context.d.ts +29 -0
  157. package/dist/host/context.d.ts.map +1 -0
  158. package/dist/host/context.js +11 -0
  159. package/dist/host/github-dir.d.ts +16 -0
  160. package/dist/host/github-dir.d.ts.map +1 -0
  161. package/dist/host/github-dir.js +164 -0
  162. package/dist/host/mcp-list.d.ts +12 -0
  163. package/dist/host/mcp-list.d.ts.map +1 -0
  164. package/dist/host/mcp-list.js +122 -0
  165. package/dist/host/requirement-ref.d.ts +12 -0
  166. package/dist/host/requirement-ref.d.ts.map +1 -0
  167. package/dist/host/requirement-ref.js +40 -0
  168. package/dist/host/run-command.d.ts +27 -0
  169. package/dist/host/run-command.d.ts.map +1 -0
  170. package/dist/host/run-command.js +66 -0
  171. package/dist/host/validate-spec.d.ts +10 -0
  172. package/dist/host/validate-spec.d.ts.map +1 -0
  173. package/dist/host/validate-spec.js +69 -0
  174. package/dist/index.d.ts +69 -0
  175. package/dist/index.d.ts.map +1 -0
  176. package/dist/index.js +41 -0
  177. package/dist/kernel/classify.d.ts +44 -0
  178. package/dist/kernel/classify.d.ts.map +1 -0
  179. package/dist/kernel/classify.js +136 -0
  180. package/dist/kernel/cli-agent.d.ts +62 -0
  181. package/dist/kernel/cli-agent.d.ts.map +1 -0
  182. package/dist/kernel/cli-agent.js +281 -0
  183. package/dist/kernel/cli-login.d.ts +13 -0
  184. package/dist/kernel/cli-login.d.ts.map +1 -0
  185. package/dist/kernel/cli-login.js +199 -0
  186. package/dist/kernel/definition.d.ts +41 -0
  187. package/dist/kernel/definition.d.ts.map +1 -0
  188. package/dist/kernel/definition.js +13 -0
  189. package/dist/kernel/env-template.d.ts +17 -0
  190. package/dist/kernel/env-template.d.ts.map +1 -0
  191. package/dist/kernel/env-template.js +78 -0
  192. package/dist/kernel/facets.d.ts +58 -0
  193. package/dist/kernel/facets.d.ts.map +1 -0
  194. package/dist/kernel/facets.js +102 -0
  195. package/dist/kernel/guard.d.ts +24 -0
  196. package/dist/kernel/guard.d.ts.map +1 -0
  197. package/dist/kernel/guard.js +69 -0
  198. package/dist/kernel/handle.d.ts +29 -0
  199. package/dist/kernel/handle.d.ts.map +1 -0
  200. package/dist/kernel/handle.js +97 -0
  201. package/dist/kernel/keyed.d.ts +19 -0
  202. package/dist/kernel/keyed.d.ts.map +1 -0
  203. package/dist/kernel/keyed.js +35 -0
  204. package/dist/kernel/login.d.ts +20 -0
  205. package/dist/kernel/login.d.ts.map +1 -0
  206. package/dist/kernel/login.js +74 -0
  207. package/dist/kernel/options.d.ts +73 -0
  208. package/dist/kernel/options.d.ts.map +1 -0
  209. package/dist/kernel/options.js +41 -0
  210. package/dist/kernel/run.d.ts +20 -0
  211. package/dist/kernel/run.d.ts.map +1 -0
  212. package/dist/kernel/run.js +245 -0
  213. package/dist/kernel/runtime.d.ts +48 -0
  214. package/dist/kernel/runtime.d.ts.map +1 -0
  215. package/dist/kernel/runtime.js +108 -0
  216. package/dist/keyring/index.d.ts +2 -0
  217. package/dist/keyring/index.d.ts.map +1 -0
  218. package/dist/keyring/index.js +1 -0
  219. package/dist/keyring/resolver.d.ts +9 -0
  220. package/dist/keyring/resolver.d.ts.map +1 -0
  221. package/dist/keyring/resolver.js +62 -0
  222. package/dist/run-agent.d.ts +19 -0
  223. package/dist/run-agent.d.ts.map +1 -0
  224. package/dist/run-agent.js +25 -0
  225. package/dist/structured/extract.d.ts +27 -0
  226. package/dist/structured/extract.d.ts.map +1 -0
  227. package/dist/structured/extract.js +188 -0
  228. package/dist/structured/run-structured.d.ts +83 -0
  229. package/dist/structured/run-structured.d.ts.map +1 -0
  230. package/dist/structured/run-structured.js +202 -0
  231. package/dist/structured/standard-schema.d.ts +34 -0
  232. package/dist/structured/standard-schema.d.ts.map +1 -0
  233. package/dist/structured/standard-schema.js +6 -0
  234. package/dist/testing/fake-agent.d.ts +32 -0
  235. package/dist/testing/fake-agent.d.ts.map +1 -0
  236. package/dist/testing/fake-agent.js +43 -0
  237. package/dist/testing/fake-clock.d.ts +8 -0
  238. package/dist/testing/fake-clock.d.ts.map +1 -0
  239. package/dist/testing/fake-clock.js +44 -0
  240. package/dist/testing/fake-exec.d.ts +26 -0
  241. package/dist/testing/fake-exec.d.ts.map +1 -0
  242. package/dist/testing/fake-exec.js +69 -0
  243. package/dist/testing/fake-secrets.d.ts +3 -0
  244. package/dist/testing/fake-secrets.d.ts.map +1 -0
  245. package/dist/testing/fake-secrets.js +5 -0
  246. package/dist/testing/index.d.ts +10 -0
  247. package/dist/testing/index.d.ts.map +1 -0
  248. package/dist/testing/index.js +9 -0
  249. package/package.json +75 -0
@@ -0,0 +1,409 @@
1
+ /**
2
+ * The fleet (design "Fleet"): failover is a strategy over an ordered
3
+ * candidate list plus a state store. The consumer decides what the
4
+ * candidates are and in which order; `attempt` is the composition seam.
5
+ * The agent handed to `attempt` is candidate-bound: auth env, model /
6
+ * effort defaults, and the linked signal are already folded in.
7
+ */
8
+ import { EMPTY_ENV_PATCH, mergeEnvPatches, } from "../core/env.js";
9
+ import { AGENT_ERROR_CODE, AgentError, isAgentError, } from "../core/error.js";
10
+ import { totalKeyedHandler } from "../kernel/keyed.js";
11
+ import { COOLDOWN_SCOPE, FAILURE_ACTION_KIND, resolveFailurePolicy, } from "./policy.js";
12
+ import { memoryFleetStore, } from "./store.js";
13
+ export function candidateFromProfile(profile, options = {}) {
14
+ return candidateWithOptions({
15
+ agentId: profile.agentId,
16
+ profile,
17
+ }, options);
18
+ }
19
+ export function candidateForAgent(agentId, options = {}) {
20
+ return candidateWithOptions({ agentId }, options);
21
+ }
22
+ function candidateWithOptions(candidate, options) {
23
+ const next = { ...candidate };
24
+ if (options.model !== undefined) {
25
+ next.model = options.model;
26
+ }
27
+ if (options.effort !== undefined) {
28
+ next.effort = options.effort;
29
+ }
30
+ return next;
31
+ }
32
+ function encodeCooldownKey(parts) {
33
+ return JSON.stringify(parts);
34
+ }
35
+ export function candidateKey(candidate) {
36
+ return encodeCooldownKey([
37
+ COOLDOWN_SCOPE.candidate,
38
+ candidate.agentId,
39
+ candidate.profile?.id ?? null,
40
+ candidate.model ?? null,
41
+ candidate.effort ?? null,
42
+ ]);
43
+ }
44
+ /**
45
+ * Store key for a profile-scoped cooldown (`CooldownScope` "profile").
46
+ * The store stays a flat key-value map, but the key is an encoded,
47
+ * branded value so raw profile/model text cannot collide by separator.
48
+ */
49
+ export function profileCooldownKey(profileId) {
50
+ return encodeCooldownKey([COOLDOWN_SCOPE.profile, profileId]);
51
+ }
52
+ export const BLOCKER_REASON = {
53
+ cooldown: "cooldown",
54
+ retryableFailure: "retryable-failure",
55
+ authInvalid: "auth-invalid",
56
+ manualAuthRequired: "manual-auth-required",
57
+ };
58
+ export const ATTEMPT_STATUS = {
59
+ completed: "completed",
60
+ failed: "failed",
61
+ skipped: "skipped",
62
+ };
63
+ export const FLEET_OUTCOME_STATUS = {
64
+ completed: "completed",
65
+ deferred: "deferred",
66
+ failed: "failed",
67
+ };
68
+ const FLEET_ATTEMPT_OPTION_KEY = "attempt";
69
+ function resolveCandidateIdentity(candidate) {
70
+ const key = candidateKey(candidate);
71
+ const profileId = candidate.profile?.id ?? null;
72
+ return {
73
+ candidate,
74
+ key,
75
+ profileId,
76
+ cooldownKeys: profileId === null ? [key] : [profileCooldownKey(profileId), key],
77
+ };
78
+ }
79
+ function combineAbortSignals(...signals) {
80
+ const active = signals.filter((signal) => signal !== undefined);
81
+ const [first] = active;
82
+ if (first === undefined) {
83
+ return undefined;
84
+ }
85
+ return active.length === 1 ? first : AbortSignal.any(active);
86
+ }
87
+ function withCandidateRunBinding(options, binding) {
88
+ const runOptions = {
89
+ ...options,
90
+ env: mergeEnvPatches(binding.authPatch, options.env ?? EMPTY_ENV_PATCH),
91
+ };
92
+ const { candidate } = binding;
93
+ if (runOptions.model === undefined && candidate.model !== undefined) {
94
+ runOptions.model = candidate.model;
95
+ }
96
+ if (runOptions.effort === undefined && candidate.effort !== undefined) {
97
+ runOptions.effort = candidate.effort;
98
+ }
99
+ const signal = combineAbortSignals(binding.fleetSignal, options.signal);
100
+ if (signal !== undefined) {
101
+ runOptions.signal = signal;
102
+ }
103
+ return runOptions;
104
+ }
105
+ function bindCandidate(agent, candidate, authPatch, fleetSignal) {
106
+ function run(options) {
107
+ return agent.run(withCandidateRunBinding(options, {
108
+ candidate,
109
+ authPatch,
110
+ fleetSignal,
111
+ }));
112
+ }
113
+ return {
114
+ ...agent,
115
+ run,
116
+ };
117
+ }
118
+ function invalidCandidate(reason) {
119
+ return new AgentError({
120
+ code: AGENT_ERROR_CODE.invalidInput,
121
+ message: reason,
122
+ data: { reason },
123
+ });
124
+ }
125
+ function completedAttempt(candidate) {
126
+ return { candidate, status: ATTEMPT_STATUS.completed };
127
+ }
128
+ function failedAttempt(candidate, error) {
129
+ return {
130
+ candidate,
131
+ status: ATTEMPT_STATUS.failed,
132
+ code: error.code,
133
+ detail: error.message,
134
+ };
135
+ }
136
+ function skippedAttempt(blocker, detail) {
137
+ return {
138
+ candidate: blocker.candidate,
139
+ status: ATTEMPT_STATUS.skipped,
140
+ reason: blocker.reason,
141
+ detail,
142
+ };
143
+ }
144
+ function cooldownFromError(error, fallbackMs, now) {
145
+ if (isAgentError(error, AGENT_ERROR_CODE.quota) ||
146
+ isAgentError(error, AGENT_ERROR_CODE.capacity)) {
147
+ if (error.data.cooldownUntil !== null) {
148
+ return error.data.cooldownUntil;
149
+ }
150
+ }
151
+ return fallbackMs === null ? null : new Date(now + fallbackMs).toISOString();
152
+ }
153
+ /**
154
+ * A candidate is blocked until every active cooldown expires, so the
155
+ * consulted levels (profile, candidate — A4) reduce to the latest one.
156
+ * ISO-8601 UTC strings compare lexicographically.
157
+ */
158
+ class CandidateReadinessResolver {
159
+ store;
160
+ clock;
161
+ constructor(store, clock) {
162
+ this.store = store;
163
+ this.clock = clock;
164
+ }
165
+ async block(identity) {
166
+ if (identity.profileId !== null &&
167
+ (await this.store.isAuthInvalid(identity.profileId))) {
168
+ const blocker = {
169
+ candidate: identity.candidate,
170
+ reason: BLOCKER_REASON.authInvalid,
171
+ until: null,
172
+ };
173
+ return {
174
+ blocker,
175
+ trace: skippedAttempt(blocker, `profile "${identity.profileId}" is marked auth-invalid`),
176
+ };
177
+ }
178
+ const cooldownUntil = await this.activeCooldownUntil(identity.cooldownKeys);
179
+ if (cooldownUntil === null) {
180
+ return null;
181
+ }
182
+ const blocker = {
183
+ candidate: identity.candidate,
184
+ reason: BLOCKER_REASON.cooldown,
185
+ until: cooldownUntil,
186
+ };
187
+ return {
188
+ blocker,
189
+ trace: skippedAttempt(blocker, `cooling down until ${cooldownUntil}`),
190
+ };
191
+ }
192
+ async activeCooldownUntil(keys) {
193
+ const now = this.clock.now();
194
+ let latest = null;
195
+ for (const key of keys) {
196
+ const until = await this.store.getCooldownUntil(key);
197
+ if (until !== null &&
198
+ Date.parse(until) > now &&
199
+ (latest === null || until > latest)) {
200
+ latest = until;
201
+ }
202
+ }
203
+ return latest;
204
+ }
205
+ }
206
+ function earliestRetryAt(blockers) {
207
+ const times = blockers
208
+ .map((blocker) => blocker.until)
209
+ .filter((until) => until !== null)
210
+ .sort();
211
+ return times[0] ?? null;
212
+ }
213
+ async function resolveAuthPatch(agent, profile, agentId) {
214
+ if (profile === undefined) {
215
+ return EMPTY_ENV_PATCH;
216
+ }
217
+ if (profile.agentId !== agentId) {
218
+ throw invalidCandidate(`Profile "${profile.id}" targets agent "${profile.agentId}", not "${agentId}"`);
219
+ }
220
+ if (agent.auth === null) {
221
+ throw invalidCandidate(`Agent "${agentId}" has no auth capability but candidate carries profile "${profile.id}"`);
222
+ }
223
+ return agent.auth.toEnv(profile.material);
224
+ }
225
+ class CandidateAgentBinder {
226
+ runtime;
227
+ constructor(runtime) {
228
+ this.runtime = runtime;
229
+ }
230
+ async bind(identity, fleetSignal) {
231
+ const agent = this.runtime.agent(identity.candidate.agentId);
232
+ const authPatch = await resolveAuthPatch(agent, identity.candidate.profile, identity.candidate.agentId);
233
+ return bindCandidate(agent, identity.candidate, authPatch, fleetSignal);
234
+ }
235
+ }
236
+ const FAILURE_DISPOSITION_KIND = {
237
+ terminal: "terminal",
238
+ blocked: "blocked",
239
+ };
240
+ const terminalPolicyCodes = new Set([
241
+ AGENT_ERROR_CODE.invalidInput,
242
+ AGENT_ERROR_CODE.cancelled,
243
+ ]);
244
+ function isPolicyCode(code) {
245
+ return !terminalPolicyCodes.has(code);
246
+ }
247
+ function terminalDisposition(error) {
248
+ return { kind: FAILURE_DISPOSITION_KIND.terminal, error };
249
+ }
250
+ function blockedDisposition(blocker) {
251
+ return { kind: FAILURE_DISPOSITION_KIND.blocked, blocker };
252
+ }
253
+ function failoverBlocker(identity, until) {
254
+ return until === null
255
+ ? {
256
+ candidate: identity.candidate,
257
+ reason: BLOCKER_REASON.retryableFailure,
258
+ until: null,
259
+ }
260
+ : {
261
+ candidate: identity.candidate,
262
+ reason: BLOCKER_REASON.cooldown,
263
+ until,
264
+ };
265
+ }
266
+ const failureActionHandlers = {
267
+ [FAILURE_ACTION_KIND.terminal]: async (_action, context) => terminalDisposition(context.error),
268
+ [FAILURE_ACTION_KIND.markAuthInvalid]: async (_action, context) => {
269
+ const { identity, store } = context;
270
+ if (identity.profileId !== null) {
271
+ await store.markAuthInvalid(identity.profileId);
272
+ }
273
+ return blockedDisposition({
274
+ candidate: identity.candidate,
275
+ reason: identity.profileId === null
276
+ ? BLOCKER_REASON.manualAuthRequired
277
+ : BLOCKER_REASON.authInvalid,
278
+ until: null,
279
+ });
280
+ },
281
+ [FAILURE_ACTION_KIND.failover]: async (action, context) => {
282
+ const { identity, error, store, clock } = context;
283
+ const until = cooldownFromError(error, action.cooldownMs, clock.now());
284
+ if (until !== null) {
285
+ const scopeKey = action.scope === COOLDOWN_SCOPE.profile && identity.profileId !== null
286
+ ? profileCooldownKey(identity.profileId)
287
+ : identity.key;
288
+ await store.setCooldownUntil(scopeKey, until);
289
+ }
290
+ return blockedDisposition(failoverBlocker(identity, until));
291
+ },
292
+ };
293
+ function failureActionHandler(action) {
294
+ // `satisfies FailureActionHandlerRegistry` above proves the record is
295
+ // total over FailureActionKind; the widening is owned by kernel/keyed.
296
+ return totalKeyedHandler(failureActionHandlers, action.kind);
297
+ }
298
+ class FailureActionApplier {
299
+ policy;
300
+ store;
301
+ clock;
302
+ constructor(policy, store, clock) {
303
+ this.policy = policy;
304
+ this.store = store;
305
+ this.clock = clock;
306
+ }
307
+ async apply(identity, error) {
308
+ if (!isPolicyCode(error.code)) {
309
+ return terminalDisposition(error);
310
+ }
311
+ const action = this.policy[error.code];
312
+ return failureActionHandler(action)(action, {
313
+ identity,
314
+ error,
315
+ store: this.store,
316
+ clock: this.clock,
317
+ });
318
+ }
319
+ }
320
+ function isFleetAttemptRunOptions(request) {
321
+ return FLEET_ATTEMPT_OPTION_KEY in request;
322
+ }
323
+ function stripRunSignal(options) {
324
+ const { signal: _signal, ...runOptions } = options;
325
+ return runOptions;
326
+ }
327
+ function normalizeFleetRunOptions(request) {
328
+ if (isFleetAttemptRunOptions(request)) {
329
+ return request;
330
+ }
331
+ const signal = combineAbortSignals(request.signal, request.runOptions.signal);
332
+ const runOptions = stripRunSignal(request.runOptions);
333
+ const normalized = {
334
+ candidates: request.candidates,
335
+ attempt: async (agent) => await agent.run(runOptions).result,
336
+ };
337
+ if (signal !== undefined) {
338
+ normalized.signal = signal;
339
+ }
340
+ return normalized;
341
+ }
342
+ export function createFleet(options) {
343
+ const store = options.store ?? memoryFleetStore();
344
+ const policy = resolveFailurePolicy(options.policy);
345
+ const { clock } = options.runtime.ports;
346
+ const readiness = new CandidateReadinessResolver(store, clock);
347
+ const binder = new CandidateAgentBinder(options.runtime);
348
+ const failures = new FailureActionApplier(policy, store, clock);
349
+ async function runCandidates(request) {
350
+ const attempts = [];
351
+ const blockers = [];
352
+ for (const candidate of request.candidates) {
353
+ if (request.signal?.aborted === true) {
354
+ return {
355
+ status: FLEET_OUTCOME_STATUS.failed,
356
+ error: new AgentError({
357
+ code: AGENT_ERROR_CODE.cancelled,
358
+ message: "Fleet run was cancelled",
359
+ data: { reason: null },
360
+ }),
361
+ attempts,
362
+ };
363
+ }
364
+ const identity = resolveCandidateIdentity(candidate);
365
+ const blocked = await readiness.block(identity);
366
+ if (blocked !== null) {
367
+ blockers.push(blocked.blocker);
368
+ attempts.push(blocked.trace);
369
+ continue;
370
+ }
371
+ try {
372
+ const bound = await binder.bind(identity, request.signal);
373
+ const value = await request.attempt(bound, candidate);
374
+ attempts.push(completedAttempt(candidate));
375
+ return {
376
+ status: FLEET_OUTCOME_STATUS.completed,
377
+ value,
378
+ candidate,
379
+ attempts,
380
+ };
381
+ }
382
+ catch (thrown) {
383
+ if (!isAgentError(thrown)) {
384
+ throw thrown;
385
+ }
386
+ attempts.push(failedAttempt(candidate, thrown));
387
+ const disposition = await failures.apply(identity, thrown);
388
+ if (disposition.kind === FAILURE_DISPOSITION_KIND.terminal) {
389
+ return {
390
+ status: FLEET_OUTCOME_STATUS.failed,
391
+ error: disposition.error,
392
+ attempts,
393
+ };
394
+ }
395
+ blockers.push(disposition.blocker);
396
+ }
397
+ }
398
+ return {
399
+ status: FLEET_OUTCOME_STATUS.deferred,
400
+ blockers,
401
+ earliestRetryAt: earliestRetryAt(blockers),
402
+ attempts,
403
+ };
404
+ }
405
+ function run(request) {
406
+ return runCandidates(normalizeFleetRunOptions(request));
407
+ }
408
+ return { run };
409
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Failure policy (design "Fleet"): a data table, overridable per code.
3
+ * `invalid_input` and `cancelled` are excluded from the type itself —
4
+ * caller bugs and aborts are unconditionally terminal, and no override
5
+ * can say otherwise. Same-candidate retry is deliberately absent from
6
+ * the action vocabulary.
7
+ */
8
+ import { AGENT_ERROR_CODE, type AgentErrorCode } from "../core/error.js";
9
+ /**
10
+ * Where a failover cooldown is written (A4: both levels are needed).
11
+ * `profile` cools every candidate sharing the credential — quota is
12
+ * account-scoped, so retrying the same profile on another model is a
13
+ * wasted attempt. `candidate` cools exactly one (agent, profile, model,
14
+ * effort) identity — capacity is model-scoped. A profile-scoped action
15
+ * on a profile-less candidate falls back to the candidate identity,
16
+ * the only credential identity that exists there.
17
+ */
18
+ export declare const COOLDOWN_SCOPE: {
19
+ readonly profile: "profile";
20
+ readonly candidate: "candidate";
21
+ };
22
+ export type CooldownScope = (typeof COOLDOWN_SCOPE)[keyof typeof COOLDOWN_SCOPE];
23
+ export declare const FAILURE_ACTION_KIND: {
24
+ readonly failover: "failover";
25
+ readonly markAuthInvalid: "mark-auth-invalid";
26
+ readonly terminal: "terminal";
27
+ };
28
+ export type FailureActionKind = (typeof FAILURE_ACTION_KIND)[keyof typeof FAILURE_ACTION_KIND];
29
+ export type FailureAction = {
30
+ kind: typeof FAILURE_ACTION_KIND.failover;
31
+ cooldownMs: number | null;
32
+ scope: CooldownScope;
33
+ } | {
34
+ kind: typeof FAILURE_ACTION_KIND.markAuthInvalid;
35
+ } | {
36
+ kind: typeof FAILURE_ACTION_KIND.terminal;
37
+ };
38
+ export type PolicyCode = Exclude<AgentErrorCode, typeof AGENT_ERROR_CODE.invalidInput | typeof AGENT_ERROR_CODE.cancelled>;
39
+ export type FailurePolicy = Record<PolicyCode, FailureAction>;
40
+ /** v1 defaults (A3/A4): quota 3600s, capacity 900s, auth sticky mark. */
41
+ export declare const QUOTA_DEFAULT_COOLDOWN_MS = 3600000;
42
+ export declare const CAPACITY_DEFAULT_COOLDOWN_MS = 900000;
43
+ export declare const defaultFailurePolicy: FailurePolicy;
44
+ export declare function resolveFailurePolicy(overrides: Partial<FailurePolicy> | undefined): FailurePolicy;
45
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../src/fleet/policy.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,gBAAgB,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEzE;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc;;;CAGjB,CAAC;AAEX,MAAM,MAAM,aAAa,GACvB,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,OAAO,cAAc,CAAC,CAAC;AAEvD,eAAO,MAAM,mBAAmB;;;;CAItB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAC3B,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,OAAO,mBAAmB,CAAC,CAAC;AAEjE,MAAM,MAAM,aAAa,GACrB;IACE,IAAI,EAAE,OAAO,mBAAmB,CAAC,QAAQ,CAAC;IAC1C,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,aAAa,CAAC;CACtB,GACD;IAAE,IAAI,EAAE,OAAO,mBAAmB,CAAC,eAAe,CAAA;CAAE,GACpD;IAAE,IAAI,EAAE,OAAO,mBAAmB,CAAC,QAAQ,CAAA;CAAE,CAAC;AAElD,MAAM,MAAM,UAAU,GAAG,OAAO,CAC9B,cAAc,EACd,OAAO,gBAAgB,CAAC,YAAY,GAAG,OAAO,gBAAgB,CAAC,SAAS,CACzE,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAE9D,yEAAyE;AACzE,eAAO,MAAM,yBAAyB,UAAY,CAAC;AACnD,eAAO,MAAM,4BAA4B,SAAU,CAAC;AAEpD,eAAO,MAAM,oBAAoB,EAAE,aAiBlC,CAAC;AAEF,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,SAAS,GAC5C,aAAa,CAEf"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Failure policy (design "Fleet"): a data table, overridable per code.
3
+ * `invalid_input` and `cancelled` are excluded from the type itself —
4
+ * caller bugs and aborts are unconditionally terminal, and no override
5
+ * can say otherwise. Same-candidate retry is deliberately absent from
6
+ * the action vocabulary.
7
+ */
8
+ import { AGENT_ERROR_CODE } from "../core/error.js";
9
+ /**
10
+ * Where a failover cooldown is written (A4: both levels are needed).
11
+ * `profile` cools every candidate sharing the credential — quota is
12
+ * account-scoped, so retrying the same profile on another model is a
13
+ * wasted attempt. `candidate` cools exactly one (agent, profile, model,
14
+ * effort) identity — capacity is model-scoped. A profile-scoped action
15
+ * on a profile-less candidate falls back to the candidate identity,
16
+ * the only credential identity that exists there.
17
+ */
18
+ export const COOLDOWN_SCOPE = {
19
+ profile: "profile",
20
+ candidate: "candidate",
21
+ };
22
+ export const FAILURE_ACTION_KIND = {
23
+ failover: "failover",
24
+ markAuthInvalid: "mark-auth-invalid",
25
+ terminal: "terminal",
26
+ };
27
+ /** v1 defaults (A3/A4): quota 3600s, capacity 900s, auth sticky mark. */
28
+ export const QUOTA_DEFAULT_COOLDOWN_MS = 3_600_000;
29
+ export const CAPACITY_DEFAULT_COOLDOWN_MS = 900_000;
30
+ export const defaultFailurePolicy = {
31
+ [AGENT_ERROR_CODE.quota]: {
32
+ kind: FAILURE_ACTION_KIND.failover,
33
+ cooldownMs: QUOTA_DEFAULT_COOLDOWN_MS,
34
+ scope: COOLDOWN_SCOPE.profile,
35
+ },
36
+ [AGENT_ERROR_CODE.capacity]: {
37
+ kind: FAILURE_ACTION_KIND.failover,
38
+ cooldownMs: CAPACITY_DEFAULT_COOLDOWN_MS,
39
+ scope: COOLDOWN_SCOPE.candidate,
40
+ },
41
+ [AGENT_ERROR_CODE.auth]: { kind: FAILURE_ACTION_KIND.markAuthInvalid },
42
+ [AGENT_ERROR_CODE.notAvailable]: { kind: FAILURE_ACTION_KIND.terminal },
43
+ [AGENT_ERROR_CODE.timeout]: { kind: FAILURE_ACTION_KIND.terminal },
44
+ [AGENT_ERROR_CODE.limit]: { kind: FAILURE_ACTION_KIND.terminal },
45
+ [AGENT_ERROR_CODE.failed]: { kind: FAILURE_ACTION_KIND.terminal },
46
+ [AGENT_ERROR_CODE.outputRejected]: { kind: FAILURE_ACTION_KIND.terminal },
47
+ };
48
+ export function resolveFailurePolicy(overrides) {
49
+ return { ...defaultFailurePolicy, ...overrides };
50
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Fleet state store port (design A4/A5): cooldowns keyed by candidate
3
+ * identity, sticky auth marks keyed by profile id. Expiry is purely
4
+ * time-based and evaluated by the fleet, not the store. The file
5
+ * implementation writes atomically (temp + rename) under an advisory
6
+ * cross-process lock with 0700/0600 modes. Fleet state is a cache:
7
+ * unknown versions are discarded and reconstructed from future runs.
8
+ */
9
+ import type { Clock } from "../core/ports.js";
10
+ declare const COOLDOWN_KEY: unique symbol;
11
+ export type CooldownKey = string & {
12
+ readonly [COOLDOWN_KEY]: true;
13
+ };
14
+ export interface FleetStateStore {
15
+ getCooldownUntil(key: CooldownKey): Promise<string | null>;
16
+ setCooldownUntil(key: CooldownKey, untilIso: string): Promise<void>;
17
+ isAuthInvalid(profileId: string): Promise<boolean>;
18
+ markAuthInvalid(profileId: string): Promise<void>;
19
+ }
20
+ export declare function memoryFleetStore(): FleetStateStore;
21
+ /**
22
+ * Lock retry pacing and stale-lock age both read the injected clock
23
+ * (inbound, rule 1: absent = production system clock) so contention and
24
+ * takeover are testable without real waiting.
25
+ */
26
+ export declare function fileFleetStore(filePath: string, clock?: Clock): FleetStateStore;
27
+ export {};
28
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/fleet/store.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAYH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAG9C,OAAO,CAAC,MAAM,YAAY,EAAE,OAAO,MAAM,CAAC;AAC1C,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,YAAY,CAAC,EAAE,IAAI,CAAA;CAAE,CAAC;AAErE,MAAM,WAAW,eAAe;IAC9B,gBAAgB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC3D,gBAAgB,CAAC,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD;AAgPD,wBAAgB,gBAAgB,IAAI,eAAe,CAElD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,KAAK,CAAC,EAAE,KAAK,GACZ,eAAe,CAEjB"}