@agentic-workflow-kit/orchestrator 0.1.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 (86) hide show
  1. package/LICENSE +21 -0
  2. package/dist/analysis/runAnalyzer.d.ts +22 -0
  3. package/dist/analysis/runAnalyzer.d.ts.map +1 -0
  4. package/dist/analysis/runAnalyzer.js +177 -0
  5. package/dist/artifacts/FileArtifactStore.d.ts +9 -0
  6. package/dist/artifacts/FileArtifactStore.d.ts.map +1 -0
  7. package/dist/artifacts/FileArtifactStore.js +21 -0
  8. package/dist/cli/args.d.ts +5 -0
  9. package/dist/cli/args.d.ts.map +1 -0
  10. package/dist/cli/args.js +213 -0
  11. package/dist/cli.d.ts +9 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +284 -0
  14. package/dist/clock/SystemClock.d.ts +6 -0
  15. package/dist/clock/SystemClock.d.ts.map +1 -0
  16. package/dist/clock/SystemClock.js +8 -0
  17. package/dist/config/configLoader.d.ts +5 -0
  18. package/dist/config/configLoader.d.ts.map +1 -0
  19. package/dist/config/configLoader.js +105 -0
  20. package/dist/config/generate-schema.d.ts +2 -0
  21. package/dist/config/generate-schema.d.ts.map +1 -0
  22. package/dist/config/generate-schema.js +8 -0
  23. package/dist/config/jsonSchema.d.ts +3 -0
  24. package/dist/config/jsonSchema.d.ts.map +1 -0
  25. package/dist/config/jsonSchema.js +44 -0
  26. package/dist/config/preset.d.ts +16 -0
  27. package/dist/config/preset.d.ts.map +1 -0
  28. package/dist/config/preset.js +14 -0
  29. package/dist/config/resolve.d.ts +12 -0
  30. package/dist/config/resolve.d.ts.map +1 -0
  31. package/dist/config/resolve.js +30 -0
  32. package/dist/config/schema.d.ts +68 -0
  33. package/dist/config/schema.d.ts.map +1 -0
  34. package/dist/config/schema.js +80 -0
  35. package/dist/drivers/StoryRunner.d.ts +24 -0
  36. package/dist/drivers/StoryRunner.d.ts.map +1 -0
  37. package/dist/drivers/StoryRunner.js +1 -0
  38. package/dist/drivers/codex-mcp/CodexMcpStoryRunner.d.ts +25 -0
  39. package/dist/drivers/codex-mcp/CodexMcpStoryRunner.d.ts.map +1 -0
  40. package/dist/drivers/codex-mcp/CodexMcpStoryRunner.js +145 -0
  41. package/dist/drivers/codex-mcp/schemaValidation.d.ts +7 -0
  42. package/dist/drivers/codex-mcp/schemaValidation.d.ts.map +1 -0
  43. package/dist/drivers/codex-mcp/schemaValidation.js +43 -0
  44. package/dist/drivers/codex-mcp/toolInput.d.ts +12 -0
  45. package/dist/drivers/codex-mcp/toolInput.d.ts.map +1 -0
  46. package/dist/drivers/codex-mcp/toolInput.js +82 -0
  47. package/dist/git/GitInspector.d.ts +34 -0
  48. package/dist/git/GitInspector.d.ts.map +1 -0
  49. package/dist/git/GitInspector.js +73 -0
  50. package/dist/index.d.ts +11 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +9 -0
  53. package/dist/internal/guards.d.ts +4 -0
  54. package/dist/internal/guards.d.ts.map +1 -0
  55. package/dist/internal/guards.js +9 -0
  56. package/dist/logging/ConsoleLogger.d.ts +7 -0
  57. package/dist/logging/ConsoleLogger.d.ts.map +1 -0
  58. package/dist/logging/ConsoleLogger.js +16 -0
  59. package/dist/metrics/aggregate.d.ts +6 -0
  60. package/dist/metrics/aggregate.d.ts.map +1 -0
  61. package/dist/metrics/aggregate.js +32 -0
  62. package/dist/metrics/liveMetrics.d.ts +16 -0
  63. package/dist/metrics/liveMetrics.d.ts.map +1 -0
  64. package/dist/metrics/liveMetrics.js +16 -0
  65. package/dist/runner/CompletionGate.d.ts +25 -0
  66. package/dist/runner/CompletionGate.d.ts.map +1 -0
  67. package/dist/runner/CompletionGate.js +49 -0
  68. package/dist/runner/MetricsCollector.d.ts +27 -0
  69. package/dist/runner/MetricsCollector.d.ts.map +1 -0
  70. package/dist/runner/MetricsCollector.js +49 -0
  71. package/dist/runner/RunJournal.d.ts +32 -0
  72. package/dist/runner/RunJournal.d.ts.map +1 -0
  73. package/dist/runner/RunJournal.js +78 -0
  74. package/dist/runner/WorkflowRunner.d.ts +45 -0
  75. package/dist/runner/WorkflowRunner.d.ts.map +1 -0
  76. package/dist/runner/WorkflowRunner.js +289 -0
  77. package/dist/scheduler/scheduler.d.ts +8 -0
  78. package/dist/scheduler/scheduler.d.ts.map +1 -0
  79. package/dist/scheduler/scheduler.js +8 -0
  80. package/dist/tracks/markdownTracker.d.ts +29 -0
  81. package/dist/tracks/markdownTracker.d.ts.map +1 -0
  82. package/dist/tracks/markdownTracker.js +349 -0
  83. package/dist/types.d.ts +222 -0
  84. package/dist/types.d.ts.map +1 -0
  85. package/dist/types.js +1 -0
  86. package/package.json +75 -0
@@ -0,0 +1,289 @@
1
+ import path from 'node:path';
2
+ import pLimit from 'p-limit';
3
+ import { buildGenericPrompt } from '../drivers/codex-mcp/toolInput.js';
4
+ import { safeName } from '../internal/guards.js';
5
+ import { selectDispatchableStories } from '../scheduler/scheduler.js';
6
+ import { CompletionGate } from './CompletionGate.js';
7
+ import { MetricsCollector } from './MetricsCollector.js';
8
+ import { RunJournal } from './RunJournal.js';
9
+ const defaultChildTimer = {
10
+ setTimeout: (callback, ms) => globalThis.setTimeout(callback, ms),
11
+ clearTimeout: (handle) => globalThis.clearTimeout(handle),
12
+ setInterval: (callback, ms) => globalThis.setInterval(callback, ms),
13
+ clearInterval: (handle) => globalThis.clearInterval(handle),
14
+ };
15
+ export class WorkflowRunner {
16
+ dependencies;
17
+ state;
18
+ metrics;
19
+ journal;
20
+ completionGate;
21
+ constructor(dependencies) {
22
+ this.dependencies = dependencies;
23
+ this.metrics = new MetricsCollector(dependencies.clock);
24
+ this.journal = new RunJournal({ artifactStore: dependencies.artifactStore, clock: dependencies.clock });
25
+ this.completionGate = new CompletionGate({
26
+ gitInspector: dependencies.gitInspector,
27
+ statuses: dependencies.config.statuses,
28
+ git: dependencies.config.git,
29
+ childCwdAbs: dependencies.config.codex.childSession.cwdAbs,
30
+ });
31
+ this.state = {
32
+ runId: dependencies.runId,
33
+ command: dependencies.command,
34
+ workspaceRoot: dependencies.config.workspace.rootAbs,
35
+ artifactDir: path.join(dependencies.config.artifacts.runsDirAbs, dependencies.runId),
36
+ status: 'running',
37
+ maxParallel: dependencies.config.orchestrator.maxParallel,
38
+ startedAt: dependencies.clock.now(),
39
+ active: [],
40
+ completed: [],
41
+ blockedStoryId: null,
42
+ blockedReason: null,
43
+ };
44
+ }
45
+ async listEligible() {
46
+ const stories = await this.dependencies.storySource.listStories();
47
+ return stories.filter((story) => story.eligible);
48
+ }
49
+ async dryRunEligible() {
50
+ await this.journal.writeRunMetadata(this.state);
51
+ await this.journal.writeConfigSnapshot(this.dependencies.config);
52
+ await this.journal.record('run-started', { command: 'run-eligible', dryRun: true });
53
+ const stories = await this.dependencies.storySource.listStories();
54
+ await this.journal.writeStorySnapshot('initial', stories);
55
+ const dispatchable = selectDispatchableStories(stories, {
56
+ maxParallel: this.dependencies.config.orchestrator.maxParallel,
57
+ });
58
+ this.state = { ...this.state, status: 'dry-run', dryRunDispatch: dispatchable.map((story) => story.id) };
59
+ await this.writeState();
60
+ await this.writeLiveMetrics();
61
+ return { ...this.state };
62
+ }
63
+ async runStory(storyId, options = {}) {
64
+ await this.journal.writeRunMetadata(this.state);
65
+ await this.journal.writeConfigSnapshot(this.dependencies.config);
66
+ await this.journal.record('run-started', { command: 'run-story', storyId, force: options.force === true });
67
+ const stories = await this.dependencies.storySource.listStories();
68
+ await this.journal.writeStorySnapshot('initial', stories);
69
+ const story = stories.find((entry) => entry.id === storyId);
70
+ if (!story) {
71
+ this.blockOnce(storyId, `story ${storyId} was not found`);
72
+ return await this.finish();
73
+ }
74
+ if (!story.eligible && options.force !== true) {
75
+ this.blockOnce(story.id, story.blockedReason ?? `story ${story.id} is not eligible`);
76
+ return await this.finish();
77
+ }
78
+ const settled = await this.launchChild(story);
79
+ if (!settled.ok) {
80
+ await this.recordSettledChild(settled);
81
+ await this.journal.record('child-error', { storyId: settled.storyId, error: settled.error });
82
+ this.blockOnce(story.id, settled.error ?? 'child session failed');
83
+ return await this.finish();
84
+ }
85
+ const evaluation = await this.processSettled(settled);
86
+ if (!evaluation.complete) {
87
+ this.blockOnce(settled.storyId, evaluation.reason);
88
+ await this.journal.record('story-not-complete', {
89
+ storyId: settled.storyId,
90
+ status: evaluation.returnedStory?.status ?? null,
91
+ });
92
+ return await this.finish();
93
+ }
94
+ await this.journal.record('child-complete', { storyId: settled.storyId, sessionId: settled.sessionId });
95
+ return await this.finish();
96
+ }
97
+ async runEligible() {
98
+ await this.journal.writeRunMetadata(this.state);
99
+ await this.journal.writeConfigSnapshot(this.dependencies.config);
100
+ await this.journal.record('run-started', {
101
+ command: 'run-eligible',
102
+ maxParallel: this.dependencies.config.orchestrator.maxParallel,
103
+ });
104
+ let stories = await this.dependencies.storySource.listStories();
105
+ await this.journal.writeStorySnapshot('initial', stories);
106
+ const active = new Map();
107
+ const limit = pLimit(this.dependencies.config.orchestrator.maxParallel);
108
+ let stopLaunching = false;
109
+ const launchAvailable = async () => {
110
+ if (stopLaunching)
111
+ return;
112
+ const dispatchable = selectDispatchableStories(stories, {
113
+ maxParallel: this.dependencies.config.orchestrator.maxParallel,
114
+ activeIds: new Set(active.keys()),
115
+ });
116
+ const launched = [];
117
+ for (const story of dispatchable) {
118
+ await this.recordChildLaunch(story);
119
+ launched.push(story);
120
+ }
121
+ for (const story of launched) {
122
+ active.set(story.id, limit(() => this.executeChild(story)));
123
+ }
124
+ };
125
+ await launchAvailable();
126
+ if (active.size === 0)
127
+ return await this.finish();
128
+ while (active.size > 0) {
129
+ const settled = await Promise.race(active.values());
130
+ active.delete(settled.storyId);
131
+ if (!settled.ok) {
132
+ await this.recordSettledChild(settled);
133
+ await this.journal.record('child-error', { storyId: settled.storyId, error: settled.error });
134
+ this.blockOnce(settled.storyId, settled.error ?? 'child session failed');
135
+ stopLaunching = this.dependencies.config.orchestrator.stopLaunchingOnBlocked;
136
+ await this.writeState();
137
+ await this.writeLiveMetrics();
138
+ await launchAvailable();
139
+ continue;
140
+ }
141
+ stories = await this.dependencies.storySource.listStories();
142
+ const evaluation = await this.processSettled(settled, stories);
143
+ if (!evaluation.complete) {
144
+ this.blockOnce(settled.storyId, evaluation.reason);
145
+ await this.journal.record('story-not-complete', {
146
+ storyId: settled.storyId,
147
+ status: evaluation.returnedStory?.status ?? null,
148
+ });
149
+ stopLaunching = this.dependencies.config.orchestrator.stopLaunchingOnBlocked;
150
+ await launchAvailable();
151
+ continue;
152
+ }
153
+ await this.journal.record('child-complete', { storyId: settled.storyId, sessionId: settled.sessionId });
154
+ await launchAvailable();
155
+ }
156
+ return await this.finish();
157
+ }
158
+ async launchChild(story) {
159
+ await this.recordChildLaunch(story);
160
+ return await this.executeChild(story);
161
+ }
162
+ async recordChildLaunch(story) {
163
+ this.metrics.start(story.id);
164
+ this.state = { ...this.state, active: [...this.state.active, story.id] };
165
+ await this.journal.record('child-launched', { storyId: story.id });
166
+ await this.writeState();
167
+ await this.writeLiveMetrics();
168
+ }
169
+ async executeChild(story) {
170
+ const timeoutMs = this.dependencies.config.orchestrator.childTimeoutMs;
171
+ const timer = this.dependencies.childTimer ?? defaultChildTimer;
172
+ const startedAtMs = this.dependencies.clock.nowMs();
173
+ const childCwd = this.dependencies.config.codex.childSession.cwdAbs;
174
+ const baseShaAtLaunch = (await this.dependencies.gitInspector.snapshotBaseSha?.({
175
+ git: this.dependencies.config.git,
176
+ cwdAbs: childCwd,
177
+ })) ?? null;
178
+ let timeoutHandle;
179
+ let heartbeatHandle;
180
+ try {
181
+ const run = this.dependencies.storyRunner.runStory({
182
+ story,
183
+ prompt: buildGenericPrompt(story, this.dependencies.config.git),
184
+ cwd: childCwd,
185
+ metadata: { runId: this.state.runId },
186
+ });
187
+ const timeout = new Promise((_, reject) => {
188
+ timeoutHandle = timer.setTimeout(() => reject(new Error('child-timeout')), timeoutMs);
189
+ });
190
+ heartbeatHandle = timer.setInterval(() => {
191
+ void this.journal.record('child-heartbeat', {
192
+ storyId: story.id,
193
+ elapsedMs: this.dependencies.clock.nowMs() - startedAtMs,
194
+ });
195
+ }, heartbeatIntervalMs(timeoutMs));
196
+ const result = await Promise.race([run, timeout]);
197
+ const completedAt = this.metrics.complete(story.id);
198
+ this.state = { ...this.state, active: this.state.active.filter((entry) => entry !== story.id) };
199
+ if (result.metrics) {
200
+ this.metrics.updateChildMetric(story.id, result.metrics);
201
+ await this.dependencies.artifactStore.writeJson(`children/${safeName(story.id)}.metrics.json`, result.metrics);
202
+ }
203
+ return {
204
+ storyId: story.id,
205
+ ok: true,
206
+ sessionId: result.sessionId,
207
+ content: result.content,
208
+ rawResult: result.rawResult,
209
+ invocation: result.invocation,
210
+ completedAt,
211
+ metrics: result.metrics,
212
+ baseShaAtLaunch,
213
+ };
214
+ }
215
+ catch (error) {
216
+ const completedAt = this.metrics.complete(story.id);
217
+ this.state = { ...this.state, active: this.state.active.filter((entry) => entry !== story.id) };
218
+ return {
219
+ storyId: story.id,
220
+ ok: false,
221
+ sessionId: null,
222
+ error: error instanceof Error ? error.message : String(error),
223
+ completedAt,
224
+ baseShaAtLaunch,
225
+ };
226
+ }
227
+ finally {
228
+ if (timeoutHandle !== undefined)
229
+ timer.clearTimeout(timeoutHandle);
230
+ if (heartbeatHandle !== undefined)
231
+ timer.clearInterval(heartbeatHandle);
232
+ }
233
+ }
234
+ async processSettled(settled, stories) {
235
+ const returnedStories = stories ?? (await this.dependencies.storySource.listStories());
236
+ await this.journal.writeStorySnapshot(`after-${settled.storyId}`, returnedStories);
237
+ const evaluation = await this.completionGate.evaluate(settled, returnedStories);
238
+ const settledWithEvidence = evaluation.commitEvidence
239
+ ? { ...settled, commitEvidence: evaluation.commitEvidence }
240
+ : settled;
241
+ await this.recordSettledChild(settledWithEvidence, evaluation.returnedStory, evaluation.complete);
242
+ return evaluation;
243
+ }
244
+ async recordSettledChild(settled, returnedStory, returnedComplete) {
245
+ const entry = await this.journal.recordSettledChild(this.metrics, settled, returnedStory, returnedComplete);
246
+ this.state = { ...this.state, completed: [...this.state.completed, entry] };
247
+ await this.writeState();
248
+ await this.writeLiveMetrics();
249
+ }
250
+ blockOnce(storyId, reason) {
251
+ if (this.state.status === 'blocked')
252
+ return;
253
+ this.state = { ...this.state, status: 'blocked', blockedStoryId: storyId, blockedReason: reason };
254
+ this.dependencies.logger.warn('run blocked', { storyId, reason });
255
+ }
256
+ async finish() {
257
+ if (this.state.status !== 'blocked' && this.state.status !== 'dry-run') {
258
+ this.state = { ...this.state, status: 'complete' };
259
+ await this.journal.record('run-complete');
260
+ }
261
+ else if (this.state.status === 'blocked') {
262
+ await this.journal.record('run-blocked', {
263
+ blockedStoryId: this.state.blockedStoryId,
264
+ blockedReason: this.state.blockedReason,
265
+ });
266
+ }
267
+ const completedAt = this.dependencies.clock.now();
268
+ const runMetrics = this.metrics.buildRunMetrics({
269
+ startedAt: this.state.startedAt,
270
+ completedAt,
271
+ completedCount: this.state.completed.length,
272
+ status: this.state.status,
273
+ blockedReason: this.state.blockedReason,
274
+ });
275
+ this.state = { ...this.state, completedAt, metrics: runMetrics };
276
+ await this.writeState();
277
+ await this.writeLiveMetrics();
278
+ return { ...this.state };
279
+ }
280
+ async writeState() {
281
+ await this.journal.writeState(this.state);
282
+ }
283
+ async writeLiveMetrics() {
284
+ await this.journal.writeLiveMetrics(this.state, this.metrics.observedChildMetrics());
285
+ }
286
+ }
287
+ function heartbeatIntervalMs(timeoutMs) {
288
+ return Math.max(1, Math.floor(timeoutMs / 4));
289
+ }
@@ -0,0 +1,8 @@
1
+ import type { WorkflowStory } from '../types.js';
2
+ export interface SelectStoriesOptions {
3
+ maxParallel: number;
4
+ activeIds?: Set<string>;
5
+ }
6
+ export declare function selectDispatchableStories(stories: WorkflowStory[], options: SelectStoriesOptions): WorkflowStory[];
7
+ export declare function isCompleteStatus(status: string | undefined, completeStatuses: string[]): boolean;
8
+ //# sourceMappingURL=scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/scheduler/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACzB;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,OAAO,EAAE,oBAAoB,GAAG,aAAa,EAAE,CAIlH;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,gBAAgB,EAAE,MAAM,EAAE,GAAG,OAAO,CAEhG"}
@@ -0,0 +1,8 @@
1
+ export function selectDispatchableStories(stories, options) {
2
+ const activeIds = options.activeIds ?? new Set();
3
+ const slots = Math.max(0, options.maxParallel - activeIds.size);
4
+ return stories.filter((story) => story.eligible && !activeIds.has(story.id)).slice(0, slots);
5
+ }
6
+ export function isCompleteStatus(status, completeStatuses) {
7
+ return status !== undefined && completeStatuses.includes(status);
8
+ }
@@ -0,0 +1,29 @@
1
+ import type { StorySource, WorkflowStory, WorkflowTrack } from '../types.js';
2
+ export interface DiscoverMarkdownTracksOptions {
3
+ workspaceRoot: string;
4
+ tracksDir: string;
5
+ archiveDir: string;
6
+ completeStatuses: string[];
7
+ eligibleStatuses: string[];
8
+ idPattern: string;
9
+ }
10
+ export interface ParseTrackerStoriesContext {
11
+ completeStatuses: Set<string>;
12
+ eligibleStatuses: Set<string>;
13
+ idPattern: RegExp;
14
+ trackId: string;
15
+ trackTitle: string;
16
+ trackerPath: string;
17
+ }
18
+ export declare class MarkdownTrackStorySource implements StorySource {
19
+ private readonly options;
20
+ private readonly trackId;
21
+ constructor(options: DiscoverMarkdownTracksOptions, trackId: string);
22
+ listStories(): Promise<WorkflowStory[]>;
23
+ }
24
+ export declare class EmptyStorySource implements StorySource {
25
+ listStories(): Promise<WorkflowStory[]>;
26
+ }
27
+ export declare function discoverMarkdownTracks(options: DiscoverMarkdownTracksOptions): Promise<WorkflowTrack[]>;
28
+ export declare function parseTrackerStories(markdown: string, context: ParseTrackerStoriesContext): WorkflowStory[];
29
+ //# sourceMappingURL=markdownTracker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdownTracker.d.ts","sourceRoot":"","sources":["../../src/tracks/markdownTracker.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAK7E,MAAM,WAAW,6BAA6B;IAC5C,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,0BAA0B;IACzC,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,wBAAyB,YAAW,WAAW;IAExD,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,OAAO,EAAE,6BAA6B,EACtC,OAAO,EAAE,MAAM;IAG5B,WAAW,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;CAM9C;AAED,qBAAa,gBAAiB,YAAW,WAAW;IAC5C,WAAW,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;CAG9C;AAED,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,6BAA6B,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CA2C7G;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,0BAA0B,GAAG,aAAa,EAAE,CA4B1G"}