@wjarka/cezarion 0.14.8 → 0.14.9-dev.1004
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/README.md +129 -2
- package/dist/application-update/bin-recovery.d.ts +5 -0
- package/dist/application-update/bin-recovery.js +96 -0
- package/dist/application-update/bin-recovery.js.map +1 -0
- package/dist/application-update/discovery.d.ts +22 -0
- package/dist/application-update/discovery.js +91 -0
- package/dist/application-update/discovery.js.map +1 -0
- package/dist/application-update/helper.d.ts +57 -0
- package/dist/application-update/helper.js +374 -0
- package/dist/application-update/helper.js.map +1 -0
- package/dist/application-update/launcher.d.ts +8 -0
- package/dist/application-update/launcher.js +53 -0
- package/dist/application-update/launcher.js.map +1 -0
- package/dist/application-update/lock.d.ts +4 -0
- package/dist/application-update/lock.js +70 -0
- package/dist/application-update/lock.js.map +1 -0
- package/dist/application-update/npm-process.d.ts +17 -0
- package/dist/application-update/npm-process.js +133 -0
- package/dist/application-update/npm-process.js.map +1 -0
- package/dist/application-update/service.d.ts +69 -0
- package/dist/application-update/service.js +456 -0
- package/dist/application-update/service.js.map +1 -0
- package/dist/artifacts/cli.d.ts +1 -0
- package/dist/artifacts/cli.js +32 -0
- package/dist/artifacts/cli.js.map +1 -0
- package/dist/artifacts/lifecycle.d.ts +4 -0
- package/dist/artifacts/lifecycle.js +42 -0
- package/dist/artifacts/lifecycle.js.map +1 -0
- package/dist/artifacts/resolve.d.ts +18 -0
- package/dist/artifacts/resolve.js +102 -0
- package/dist/artifacts/resolve.js.map +1 -0
- package/dist/artifacts/store.d.ts +8 -0
- package/dist/artifacts/store.js +323 -0
- package/dist/artifacts/store.js.map +1 -0
- package/dist/automations/scheduler.d.ts +6 -0
- package/dist/automations/scheduler.js +45 -17
- package/dist/automations/scheduler.js.map +1 -1
- package/dist/automations/store.d.ts +12 -4
- package/dist/automations/store.js +199 -22
- package/dist/automations/store.js.map +1 -1
- package/dist/automations/types.d.ts +2 -0
- package/dist/automations/types.js +1 -0
- package/dist/automations/types.js.map +1 -1
- package/dist/ci-wait/mcp.d.ts +3 -0
- package/dist/ci-wait/mcp.js +14 -3
- package/dist/ci-wait/mcp.js.map +1 -1
- package/dist/contract/application-update.d.ts +32 -0
- package/dist/contract/artifacts.d.ts +83 -0
- package/dist/contract/ask-schema.d.ts +32 -0
- package/dist/contract/ask.d.ts +2 -32
- package/dist/contract/automations.d.ts +6 -0
- package/dist/contract/conversations.d.ts +165 -0
- package/dist/contract/delegation.d.ts +68 -0
- package/dist/contract/discovery.d.ts +134 -0
- package/dist/contract/events.d.ts +49 -0
- package/dist/contract/health.d.ts +12 -0
- package/dist/contract/index.d.ts +3 -0
- package/dist/contract/index.js +1654 -1454
- package/dist/contract/projects.d.ts +40 -0
- package/dist/contract/runs.d.ts +186 -0
- package/dist/contract/skills.d.ts +33 -0
- package/dist/core/agent-runner.d.ts +33 -4
- package/dist/core/agent-runner.js +5 -0
- package/dist/core/agent-runner.js.map +1 -1
- package/dist/core/claude-cli-runner.d.ts +6 -12
- package/dist/core/claude-cli-runner.js +90 -20
- package/dist/core/claude-cli-runner.js.map +1 -1
- package/dist/core/codex-app-server-runner.d.ts +2 -1
- package/dist/core/codex-app-server-runner.js +327 -92
- package/dist/core/codex-app-server-runner.js.map +1 -1
- package/dist/core/codex-app-server-transport.d.ts +13 -1
- package/dist/core/codex-app-server-transport.js +58 -17
- package/dist/core/codex-app-server-transport.js.map +1 -1
- package/dist/core/cursor-acp-runner.d.ts +6 -1
- package/dist/core/cursor-acp-runner.js +162 -26
- package/dist/core/cursor-acp-runner.js.map +1 -1
- package/dist/core/cursor-provider-error.d.ts +3 -0
- package/dist/core/cursor-provider-error.js +16 -1
- package/dist/core/cursor-provider-error.js.map +1 -1
- package/dist/core/input-submissions.d.ts +14 -0
- package/dist/core/input-submissions.js +26 -0
- package/dist/core/input-submissions.js.map +1 -0
- package/dist/core/opencode-server-runner.d.ts +4 -1
- package/dist/core/opencode-server-runner.js +156 -26
- package/dist/core/opencode-server-runner.js.map +1 -1
- package/dist/core/pi-runner.d.ts +2 -1
- package/dist/core/pi-runner.js +88 -10
- package/dist/core/pi-runner.js.map +1 -1
- package/dist/core/provider-auth.d.ts +99 -1
- package/dist/core/provider-auth.js +203 -2
- package/dist/core/provider-auth.js.map +1 -1
- package/dist/delegation/cli.d.ts +1 -0
- package/dist/delegation/cli.js +103 -7
- package/dist/delegation/cli.js.map +1 -1
- package/dist/delegation/conversations.js +13 -5
- package/dist/delegation/conversations.js.map +1 -1
- package/dist/delegation/input.d.ts +2 -1
- package/dist/delegation/input.js +6 -1
- package/dist/delegation/input.js.map +1 -1
- package/dist/delegation/policy.d.ts +3 -2
- package/dist/delegation/policy.js +3 -1
- package/dist/delegation/policy.js.map +1 -1
- package/dist/delegation/process-liveness.d.ts +66 -0
- package/dist/delegation/process-liveness.js +215 -0
- package/dist/delegation/process-liveness.js.map +1 -0
- package/dist/delegation/provision.d.ts +1 -1
- package/dist/delegation/provision.js +10 -5
- package/dist/delegation/provision.js.map +1 -1
- package/dist/delegation/questions.d.ts +19 -0
- package/dist/delegation/questions.js +45 -0
- package/dist/delegation/questions.js.map +1 -0
- package/dist/delegation/results.d.ts +1 -1
- package/dist/delegation/results.js +33 -19
- package/dist/delegation/results.js.map +1 -1
- package/dist/delegation/routes.d.ts +185 -11
- package/dist/delegation/routes.js +6 -2
- package/dist/delegation/routes.js.map +1 -1
- package/dist/delegation/service.d.ts +16 -5
- package/dist/delegation/service.js +161 -13
- package/dist/delegation/service.js.map +1 -1
- package/dist/delegation/transport.d.ts +228 -65
- package/dist/discovery/catalog.d.ts +14 -0
- package/dist/discovery/catalog.js +31 -0
- package/dist/discovery/catalog.js.map +1 -0
- package/dist/discovery/cli.d.ts +8 -0
- package/dist/discovery/cli.js +104 -0
- package/dist/discovery/cli.js.map +1 -0
- package/dist/git-worktree.d.ts +3 -1
- package/dist/git-worktree.js +26 -4
- package/dist/git-worktree.js.map +1 -1
- package/dist/index.js +92 -26
- package/dist/index.js.map +1 -1
- package/dist/open-url.d.ts +2 -0
- package/dist/open-url.js +20 -0
- package/dist/open-url.js.map +1 -0
- package/dist/release/snapshot.d.ts +3 -3
- package/dist/release/snapshot.js +6 -6
- package/dist/release/snapshot.js.map +1 -1
- package/dist/runs/agent-tmpdir.d.ts +2 -0
- package/dist/runs/agent-tmpdir.js +5 -2
- package/dist/runs/agent-tmpdir.js.map +1 -1
- package/dist/runs/client-request.d.ts +11 -0
- package/dist/runs/client-request.js +26 -0
- package/dist/runs/client-request.js.map +1 -0
- package/dist/runs/delegation-state.d.ts +17 -0
- package/dist/runs/event-history.js +6 -0
- package/dist/runs/event-history.js.map +1 -1
- package/dist/runs/retention.d.ts +7 -2
- package/dist/runs/retention.js +39 -7
- package/dist/runs/retention.js.map +1 -1
- package/dist/runs/store.d.ts +100 -6
- package/dist/runs/store.js +245 -2
- package/dist/runs/store.js.map +1 -1
- package/dist/runs/webhook.d.ts +125 -0
- package/dist/runs/webhook.js +256 -0
- package/dist/runs/webhook.js.map +1 -0
- package/dist/server/provider-auth-runtime.d.ts +30 -4
- package/dist/server/provider-auth-runtime.js +71 -6
- package/dist/server/provider-auth-runtime.js.map +1 -1
- package/dist/server/server.d.ts +8226 -4860
- package/dist/server/server.js +309 -15
- package/dist/server/server.js.map +1 -1
- package/dist/server-install/platforms/ubuntu-vps.d.ts +5 -1
- package/dist/server-install/platforms/ubuntu-vps.js +160 -18
- package/dist/server-install/platforms/ubuntu-vps.js.map +1 -1
- package/dist/server-install/steps.d.ts +4 -2
- package/dist/server-install/steps.js +5 -4
- package/dist/server-install/steps.js.map +1 -1
- package/dist/skills-remote.d.ts +30 -5
- package/dist/skills-remote.js +122 -32
- package/dist/skills-remote.js.map +1 -1
- package/dist/skills.js +15 -6
- package/dist/skills.js.map +1 -1
- package/dist/task-cli/cli.d.ts +38 -0
- package/dist/task-cli/cli.js +580 -0
- package/dist/task-cli/cli.js.map +1 -0
- package/dist/task-cli/discovery.d.ts +22 -0
- package/dist/task-cli/discovery.js +96 -0
- package/dist/task-cli/discovery.js.map +1 -0
- package/dist/task-cli/http.d.ts +46 -0
- package/dist/task-cli/http.js +101 -0
- package/dist/task-cli/http.js.map +1 -0
- package/dist/task-cli/projections.d.ts +40 -0
- package/dist/task-cli/projections.js +45 -0
- package/dist/task-cli/projections.js.map +1 -0
- package/dist/task-cli/watch.d.ts +78 -0
- package/dist/task-cli/watch.js +336 -0
- package/dist/task-cli/watch.js.map +1 -0
- package/dist/workflows/run.d.ts +160 -2
- package/dist/workflows/run.js +1141 -108
- package/dist/workflows/run.js.map +1 -1
- package/dist/workspace/agent-profiles.d.ts +22 -0
- package/dist/workspace/agent-profiles.js +15 -0
- package/dist/workspace/agent-profiles.js.map +1 -1
- package/dist/workspace/config.d.ts +9 -0
- package/dist/workspace/config.js +13 -1
- package/dist/workspace/config.js.map +1 -1
- package/dist/workspace/projects-cli.js +1 -1
- package/dist/workspace/projects.d.ts +24 -1
- package/dist/workspace/projects.js +14 -1
- package/dist/workspace/projects.js.map +1 -1
- package/package.json +5 -5
- package/scripts/mock-claude.mjs +64 -2
- package/scripts/mock-codex-app-server.mjs +124 -1
- package/scripts/mock-cursor-acp.mjs +54 -3
- package/scripts/mock-cursor-hang-stdin-late.mjs +35 -0
- package/scripts/mock-cursor-hang-stdin.mjs +30 -0
- package/scripts/mock-opencode-serve.mjs +102 -2
- package/scripts/mock-pi-rpc.mjs +82 -3
- package/web/dist/assets/agent-accounts-CerclGiO.js +1 -0
- package/web/dist/assets/centered-state-DH8qtCz-.js +43 -0
- package/web/dist/assets/collapsible-De2G0xqQ.js +1 -0
- package/web/dist/assets/commit-list-CQubP1iS.js +1 -0
- package/web/dist/assets/compare-variants-C_NijJvg.js +1 -0
- package/web/dist/assets/{diff-tRE59UNb2.js → diff-8M5HlDxq2.js} +2 -2
- package/web/dist/assets/diff-controls-B6ok0TtQ.js +1 -0
- package/web/dist/assets/{diff-stat-Cgnl_hYF.js → diff-stat-D1AODN2a.js} +1 -1
- package/web/dist/assets/{diff-view-DN1ort7Q.js → diff-view-Bs5Gh7UF.js} +2 -2
- package/web/dist/assets/dropdown-menu-7KGQMoFj.js +1 -0
- package/web/dist/assets/git-toolbar-DqbapMXr.js +1 -0
- package/web/dist/assets/github-lMHiuYSx.js +1 -0
- package/web/dist/assets/{image-preview-C2JGcBlC.js → image-preview-jzHoEyLM.js} +1 -1
- package/web/dist/assets/index-D4OJTArJ.js +7 -0
- package/web/dist/assets/index-D5NkwqY7.css +1 -0
- package/web/dist/assets/markdown-DvbymqXz.js +2 -0
- package/web/dist/assets/pill-BL6ugwXo.js +1 -0
- package/web/dist/assets/project-router-ChC5Rb-Y.js +1 -0
- package/web/dist/assets/prompt-templates-j8qlK7c-.js +15 -0
- package/web/dist/assets/repo-git-BbPAgkqb.js +1 -0
- package/web/dist/assets/{run-diff-CyG0Nciv.js → run-diff-BuATgZpx.js} +2 -2
- package/web/dist/assets/run-header-DuAWswYM.js +1 -0
- package/web/dist/assets/skills-DiC2XrU1.js +1 -0
- package/web/dist/assets/{tab-link-BWNp3mLY.js → tab-link-BmGKj9Rt.js} +1 -1
- package/web/dist/assets/task-changes-CP9kPnsy.js +1 -0
- package/web/dist/assets/task-commits-D6NfTw5r.js +1 -0
- package/web/dist/assets/task-files-W-b_gtcb.js +2 -0
- package/web/dist/assets/task-thread-Bw_nQuTx.css +1 -0
- package/web/dist/assets/task-thread-Q7Nj33DH.js +9 -0
- package/web/dist/assets/textarea-ByZ-txS7.js +1 -0
- package/web/dist/assets/thread-loading-DHT9p5o5.js +1 -0
- package/web/dist/assets/token-metrics-k4XoovDS.js +1 -0
- package/web/dist/assets/utils-BTFBhZ55.js +64 -0
- package/web/dist/assets/workflows-DqQszPHJ.js +11 -0
- package/web/dist/assets/{zoomable-image-Z3JDnm0b.js → zoomable-image-Ba6y6Wyl.js} +1 -1
- package/web/dist/index.html +14 -14
- package/web/dist/assets/agent-accounts-BZm_2WAv.js +0 -1
- package/web/dist/assets/alert-dialog-B422RI-k.js +0 -43
- package/web/dist/assets/collapsible-Cy_Fmkrd.js +0 -1
- package/web/dist/assets/commit-list-Dcnk7TnI.js +0 -1
- package/web/dist/assets/compare-variants-Ciqwvd_f.js +0 -1
- package/web/dist/assets/diff-controls-BGrXGi7O.js +0 -1
- package/web/dist/assets/dropdown-menu-dwDGT8MM.js +0 -1
- package/web/dist/assets/git-toolbar-UiHnGvE0.js +0 -1
- package/web/dist/assets/github-D-MrkbJ7.js +0 -1
- package/web/dist/assets/ibm-plex-mono-regular-BF3vfe7_.ttf +0 -0
- package/web/dist/assets/index-BszfYvBq.js +0 -7
- package/web/dist/assets/index-CWh4pKP3.css +0 -1
- package/web/dist/assets/markdown-C_IcKVQd.js +0 -2
- package/web/dist/assets/pill-BsbO6G7y.js +0 -1
- package/web/dist/assets/project-router-DbLkll25.js +0 -1
- package/web/dist/assets/prompt-templates-BlTGJB5W.js +0 -15
- package/web/dist/assets/repo-git-Bm3s6Why.js +0 -1
- package/web/dist/assets/run-header-IFyIGCey.js +0 -1
- package/web/dist/assets/skills-C-C4DVFY.js +0 -1
- package/web/dist/assets/task-changes-D2PHtWUW.js +0 -1
- package/web/dist/assets/task-commits-N5u9U0_R.js +0 -1
- package/web/dist/assets/task-files-BS7c7CFm.js +0 -2
- package/web/dist/assets/task-thread-BQFdz1dJ.css +0 -1
- package/web/dist/assets/task-thread-DGM2dZV-.js +0 -9
- package/web/dist/assets/textarea-FjaU370i.js +0 -1
- package/web/dist/assets/thread-loading-77sJ8Yu_.js +0 -1
- package/web/dist/assets/token-metrics-Clxh03bE.js +0 -1
- package/web/dist/assets/utils-PYYcpCFZ.js +0 -64
- package/web/dist/assets/workflows-Czl0ZfXH.js +0 -11
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { constants } from 'node:fs';
|
|
2
|
+
import { open, realpath, lstat } from 'node:fs/promises';
|
|
3
|
+
import { isAbsolute, relative, resolve, sep } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { FILE_CONTENT_CAP, imageMimeType, isOsOpenableImage } from '../server/git-changes.js';
|
|
6
|
+
import { artifactDirectory, listArtifacts, readArtifact } from './store.js';
|
|
7
|
+
const IMAGE_CAP = 10 * 1024 * 1024;
|
|
8
|
+
const contained = (root, path) => path === root || path.startsWith(root + sep);
|
|
9
|
+
function privatePath(root, target) {
|
|
10
|
+
const segments = relative(root, target).split(sep);
|
|
11
|
+
return segments.includes('.git') || segments.some((part, i) => part === '.ai' && segments[i + 1] === 'cezar');
|
|
12
|
+
}
|
|
13
|
+
export function rasterMime(path) { return isOsOpenableImage(path) ? imageMimeType(path) : null; }
|
|
14
|
+
export function artifactPreview(artifact, bytes) {
|
|
15
|
+
const binary = bytes.subarray(0, 8192).includes(0);
|
|
16
|
+
const tooLarge = bytes.length > FILE_CONTENT_CAP;
|
|
17
|
+
return { type: 'file', source: 'artifact', artifact, path: artifact.name, size: bytes.length, binary, tooLarge,
|
|
18
|
+
...(!binary && !tooLarge ? { content: bytes.toString('utf8') } : {}) };
|
|
19
|
+
}
|
|
20
|
+
/** No outside source is opened or even stat'ed: it can only match a published snapshot. */
|
|
21
|
+
export async function loadFileLink(ctx) {
|
|
22
|
+
const unavailable = (reason) => ({ preview: { type: 'unavailable', path: ctx.path, reason } });
|
|
23
|
+
let path;
|
|
24
|
+
try {
|
|
25
|
+
if (!ctx.path || ctx.path.length > 8192 || /[\u0000-\u001f]/.test(ctx.path))
|
|
26
|
+
return unavailable('Invalid file path.');
|
|
27
|
+
if (/^file:/i.test(ctx.path))
|
|
28
|
+
path = fileURLToPath(new URL(ctx.path));
|
|
29
|
+
else {
|
|
30
|
+
if (/^[a-z][a-z\d+.-]*:/i.test(ctx.path) && !/^[a-z]:[\\/]/i.test(ctx.path))
|
|
31
|
+
return unavailable('Not a local file path.');
|
|
32
|
+
// Query decoding has already happened. This is a literal filesystem path;
|
|
33
|
+
// decoding it again would turn a filename containing %20 into a space.
|
|
34
|
+
path = ctx.path;
|
|
35
|
+
}
|
|
36
|
+
if (/[\u0000-\u001f]/.test(path) || path.startsWith('//') || path.startsWith('\\\\'))
|
|
37
|
+
return unavailable('Network file paths are not supported.');
|
|
38
|
+
if (process.platform !== 'win32' && /^[a-z]:[\\/]/i.test(path))
|
|
39
|
+
return unavailable('This path belongs to a different operating system.');
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return unavailable('Invalid local file URL or encoding.');
|
|
43
|
+
}
|
|
44
|
+
if (!isAbsolute(path) && !ctx.workingDirectory)
|
|
45
|
+
return unavailable('This task has no working directory.');
|
|
46
|
+
const target = resolve(ctx.workingDirectory ?? ctx.projectRoot, path);
|
|
47
|
+
const working = ctx.workingDirectory ? resolve(ctx.workingDirectory) : undefined;
|
|
48
|
+
const project = resolve(ctx.projectRoot);
|
|
49
|
+
const root = working && contained(working, target) ? working : contained(project, target) ? project : undefined;
|
|
50
|
+
if (!root) {
|
|
51
|
+
const directory = artifactDirectory(ctx.dataDir, ctx.runId);
|
|
52
|
+
const matches = (await listArtifacts(directory, ctx.runId)).filter(item => item.sourcePath === target)
|
|
53
|
+
.sort((a, b) => b.createdAt.localeCompare(a.createdAt) || b.id.localeCompare(a.id));
|
|
54
|
+
const entry = matches[0] ? await readArtifact(directory, ctx.runId, matches[0].id) : null;
|
|
55
|
+
if (entry)
|
|
56
|
+
return { preview: artifactPreview(entry.metadata, entry.bytes), ...(rasterMime(entry.metadata.name) ? { imageBytes: entry.bytes } : {}) };
|
|
57
|
+
return { preview: { type: 'unpublished', path: ctx.path, reason: 'This file is outside the project. Ask the agent to publish it with cez artifact publish <path> and share the returned link.' } };
|
|
58
|
+
}
|
|
59
|
+
if (privatePath(root, target)) {
|
|
60
|
+
return unavailable('Git internals and private Cezar state are not available through file links.');
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const info = await lstat(target);
|
|
64
|
+
if (info.isDirectory())
|
|
65
|
+
return unavailable('Choose a file inside this directory.');
|
|
66
|
+
if (!info.isFile() || info.isSymbolicLink())
|
|
67
|
+
return unavailable('Only regular files can be previewed; symlinks are not served.');
|
|
68
|
+
const [realRoot, actual] = await Promise.all([realpath(root), realpath(target)]);
|
|
69
|
+
if (!contained(realRoot, actual) || privatePath(realRoot, actual))
|
|
70
|
+
return unavailable('The file resolves outside the permitted directory or into private state.');
|
|
71
|
+
const handle = await open(actual, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
72
|
+
try {
|
|
73
|
+
const stat = await handle.stat();
|
|
74
|
+
if (!stat.isFile())
|
|
75
|
+
return unavailable('Not a regular file.');
|
|
76
|
+
const raster = rasterMime(target);
|
|
77
|
+
const cap = raster ? IMAGE_CAP : FILE_CONTENT_CAP;
|
|
78
|
+
const bytes = Buffer.alloc(Math.min(stat.size, cap) + 1);
|
|
79
|
+
let length = 0;
|
|
80
|
+
while (length < bytes.length) {
|
|
81
|
+
const { bytesRead } = await handle.read(bytes, length, bytes.length - length, length);
|
|
82
|
+
if (!bytesRead)
|
|
83
|
+
break;
|
|
84
|
+
length += bytesRead;
|
|
85
|
+
}
|
|
86
|
+
const sample = bytes.subarray(0, length);
|
|
87
|
+
const binary = sample.subarray(0, 8192).includes(0);
|
|
88
|
+
const tooLarge = stat.size > FILE_CONTENT_CAP || length > FILE_CONTENT_CAP;
|
|
89
|
+
const preview = { type: 'file', source: root === working ? 'worktree' : 'project', path: target, size: stat.size, binary, tooLarge,
|
|
90
|
+
...(!binary && !tooLarge ? { content: sample.toString('utf8') } : {}) };
|
|
91
|
+
return { preview, ...(raster && stat.size <= IMAGE_CAP && length <= IMAGE_CAP ? { imageBytes: sample } : {}) };
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
await handle.close();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return unavailable('The file or its working directory is missing or cannot be read.');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export async function resolveFileLink(ctx) { return (await loadFileLink(ctx)).preview; }
|
|
102
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/artifacts/resolve.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type PublishedArtifact } from '../contract/index.js';
|
|
2
|
+
export declare function artifactDirectory(dataDir: string, runId: string): string;
|
|
3
|
+
export declare function publishArtifact(directory: string, runId: string, source: string): Promise<PublishedArtifact>;
|
|
4
|
+
export declare function readArtifact(directory: string, runId: string, id: string): Promise<{
|
|
5
|
+
metadata: PublishedArtifact;
|
|
6
|
+
bytes: Buffer;
|
|
7
|
+
} | null>;
|
|
8
|
+
export declare function listArtifacts(directory: string, runId: string): Promise<PublishedArtifact[]>;
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { randomUUID, createHash } from 'node:crypto';
|
|
2
|
+
import { constants, realpathSync } from 'node:fs';
|
|
3
|
+
import { mkdir, open, opendir, realpath, lstat, rename, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { basename, isAbsolute, join, parse, resolve, sep } from 'node:path';
|
|
5
|
+
import { hostname } from 'node:os';
|
|
6
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { publishedArtifactSchema } from '../contract/index.js';
|
|
9
|
+
const MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
10
|
+
const MAX_METADATA_BYTES = 32 * 1024;
|
|
11
|
+
const MAX_RUN_BYTES = 64 * 1024 * 1024;
|
|
12
|
+
const MAX_ARTIFACTS = 64;
|
|
13
|
+
const MAX_DIRECTORY_ENTRIES = 1024;
|
|
14
|
+
const readFlags = constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK;
|
|
15
|
+
function validateContext(directory, runId) {
|
|
16
|
+
z.uuid().parse(runId);
|
|
17
|
+
if (!isAbsolute(directory) || basename(directory) !== `${runId}-artifacts`)
|
|
18
|
+
throw new Error('Invalid artifact task directory');
|
|
19
|
+
}
|
|
20
|
+
export function artifactDirectory(dataDir, runId) {
|
|
21
|
+
z.uuid().parse(runId);
|
|
22
|
+
// The server's trusted data root may have an OS alias (e.g. macOS /var).
|
|
23
|
+
// Only canonicalize that root; runs/task entry redirects remain forbidden.
|
|
24
|
+
let root = resolve(dataDir);
|
|
25
|
+
try {
|
|
26
|
+
root = realpathSync(root);
|
|
27
|
+
}
|
|
28
|
+
catch { /* absent storage reads as unavailable */ }
|
|
29
|
+
return join(root, 'runs', `${runId}-artifacts`);
|
|
30
|
+
}
|
|
31
|
+
function sameFile(a, b) {
|
|
32
|
+
return a.dev === b.dev && a.ino === b.ino;
|
|
33
|
+
}
|
|
34
|
+
function sameSnapshot(a, b) {
|
|
35
|
+
return sameFile(a, b) && a.size === b.size && a.mtimeNs === b.mtimeNs && a.ctimeNs === b.ctimeNs && a.nlink === b.nlink;
|
|
36
|
+
}
|
|
37
|
+
/** Never use readFile on untrusted descriptors: a growing file otherwise bypasses the size check. */
|
|
38
|
+
async function boundedRead(path, limit, stored = false) {
|
|
39
|
+
const file = await open(path, readFlags);
|
|
40
|
+
try {
|
|
41
|
+
const before = await file.stat({ bigint: true });
|
|
42
|
+
if (!before.isFile() || before.size > BigInt(limit) || (stored && before.nlink !== 1n))
|
|
43
|
+
throw new Error('Artifact must be a bounded regular file');
|
|
44
|
+
const buffer = Buffer.alloc(Number(before.size) + 1);
|
|
45
|
+
let count = 0;
|
|
46
|
+
while (count < buffer.length) {
|
|
47
|
+
const { bytesRead } = await file.read(buffer, count, buffer.length - count, count);
|
|
48
|
+
if (bytesRead === 0)
|
|
49
|
+
break;
|
|
50
|
+
count += bytesRead;
|
|
51
|
+
}
|
|
52
|
+
const after = await file.stat({ bigint: true });
|
|
53
|
+
const current = await lstat(path, { bigint: true });
|
|
54
|
+
if (count !== Number(before.size) || !sameSnapshot(before, after) || !sameFile(after, current) || current.isSymbolicLink())
|
|
55
|
+
throw new Error('File changed while publishing or reading artifact');
|
|
56
|
+
return buffer.subarray(0, count);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
await file.close();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** Reject symlinks in every storage component. Linux operations stay anchored to open directory
|
|
63
|
+
* descriptors; other platforms revalidate identity before/after use, not a same-user sandbox. */
|
|
64
|
+
async function storageDirectory(path, create = false) {
|
|
65
|
+
const absolute = resolve(path);
|
|
66
|
+
let file;
|
|
67
|
+
const root = parse(absolute).root;
|
|
68
|
+
let current = root;
|
|
69
|
+
try {
|
|
70
|
+
file = await open(root, readFlags | constants.O_DIRECTORY);
|
|
71
|
+
for (const part of absolute.slice(root.length).split(sep).filter(Boolean)) {
|
|
72
|
+
current = join(current, part);
|
|
73
|
+
const anchored = process.platform === 'linux' ? join(`/proc/self/fd/${file.fd}`, part) : current;
|
|
74
|
+
if (create)
|
|
75
|
+
await mkdir(anchored, { mode: 0o700 }).catch(error => { if (error.code !== 'EEXIST')
|
|
76
|
+
throw error; });
|
|
77
|
+
const next = await open(anchored, readFlags | constants.O_DIRECTORY);
|
|
78
|
+
await file.close();
|
|
79
|
+
file = next;
|
|
80
|
+
}
|
|
81
|
+
const handle = file;
|
|
82
|
+
const original = await handle.stat({ bigint: true });
|
|
83
|
+
const check = async () => {
|
|
84
|
+
if (await realpath(absolute) !== absolute || !sameFile(original, await lstat(absolute, { bigint: true })))
|
|
85
|
+
throw new Error('Redirected artifact storage');
|
|
86
|
+
};
|
|
87
|
+
await check();
|
|
88
|
+
return { path: process.platform === 'linux' ? `/proc/self/fd/${handle.fd}` : absolute, check, close: () => handle.close() };
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
await file?.close();
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function* entries(path) {
|
|
96
|
+
const stream = await opendir(path);
|
|
97
|
+
let count = 0;
|
|
98
|
+
for await (const entry of stream) {
|
|
99
|
+
if (++count > MAX_DIRECTORY_ENTRIES)
|
|
100
|
+
throw new Error('Artifact directory entry limit exceeded');
|
|
101
|
+
yield entry.name;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const ownerSchema = z.object({ pid: z.number().int().positive().max(2147483647), host: z.string().min(1).max(255) }).strict();
|
|
105
|
+
async function reclaimDeadLock(root) {
|
|
106
|
+
let lock;
|
|
107
|
+
try {
|
|
108
|
+
const path = join(root.path, '.publish.lock');
|
|
109
|
+
lock = await open(path, readFlags | constants.O_DIRECTORY);
|
|
110
|
+
const pinned = process.platform === 'linux' ? `/proc/self/fd/${lock.fd}` : path;
|
|
111
|
+
const owner = ownerSchema.parse(JSON.parse((await boundedRead(join(pinned, 'owner.json'), 1024, true)).toString('utf8')));
|
|
112
|
+
if (owner.host !== hostname())
|
|
113
|
+
return;
|
|
114
|
+
try {
|
|
115
|
+
process.kill(owner.pid, 0);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (error.code !== 'ESRCH')
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Only one reaper can claim this inode. Keep its marker through the rename, so another
|
|
123
|
+
// observer of the old lock cannot remove a newly acquired lock (the stale-unlink race).
|
|
124
|
+
await mkdir(join(pinned, '.reaping'), { mode: 0o700 });
|
|
125
|
+
if (!sameFile(await lock.stat({ bigint: true }), await lstat(path, { bigint: true })))
|
|
126
|
+
return;
|
|
127
|
+
await root.check();
|
|
128
|
+
const retired = join(root.path, `.retired-lock-${randomUUID()}`);
|
|
129
|
+
await rename(path, retired);
|
|
130
|
+
await rm(retired, { recursive: true, force: true });
|
|
131
|
+
}
|
|
132
|
+
catch { /* Unknown owner, permission failure, or another reaper: fail closed and retry. */ }
|
|
133
|
+
finally {
|
|
134
|
+
await lock?.close();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async function acquireLock(root) {
|
|
138
|
+
const candidate = join(root.path, `.lock-candidate-${randomUUID()}`);
|
|
139
|
+
const path = join(root.path, '.publish.lock');
|
|
140
|
+
await mkdir(candidate, { mode: 0o700 });
|
|
141
|
+
try {
|
|
142
|
+
await writeFile(join(candidate, 'owner.json'), JSON.stringify({ pid: process.pid, host: hostname() }), { flag: 'wx', mode: 0o600 });
|
|
143
|
+
const identity = await lstat(candidate, { bigint: true });
|
|
144
|
+
const deadline = performance.now() + 5000;
|
|
145
|
+
for (;;) {
|
|
146
|
+
await root.check();
|
|
147
|
+
try {
|
|
148
|
+
// rename may replace an empty directory; an existing ownerless lock is not permission
|
|
149
|
+
// to publish. Cooperating publishers only introduce non-empty locks atomically.
|
|
150
|
+
const exists = await lstat(path).then(() => true, (error) => {
|
|
151
|
+
if (error.code === 'ENOENT')
|
|
152
|
+
return false;
|
|
153
|
+
throw error;
|
|
154
|
+
});
|
|
155
|
+
if (!exists) {
|
|
156
|
+
await rename(candidate, path);
|
|
157
|
+
return async () => {
|
|
158
|
+
if (sameFile(identity, await lstat(path, { bigint: true })))
|
|
159
|
+
await rm(path, { recursive: true, force: true });
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
if (!['EEXIST', 'ENOTEMPTY'].includes(error.code ?? ''))
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
if (performance.now() >= deadline)
|
|
168
|
+
throw new Error('Artifact publication lock is busy or has an unverifiable owner');
|
|
169
|
+
await reclaimDeadLock(root);
|
|
170
|
+
await delay(20);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
await rm(candidate, { recursive: true, force: true });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async function checkCapacity(root, incomingBytes) {
|
|
178
|
+
let count = 0;
|
|
179
|
+
let bytes = 0;
|
|
180
|
+
for await (const id of entries(root.path)) {
|
|
181
|
+
if (!z.uuid().safeParse(id).success && !/^\.pending-[0-9a-f-]{36}$/i.test(id))
|
|
182
|
+
continue;
|
|
183
|
+
if (++count >= MAX_ARTIFACTS)
|
|
184
|
+
throw new Error('Artifact count limit exceeded');
|
|
185
|
+
let entry;
|
|
186
|
+
try {
|
|
187
|
+
entry = await open(join(root.path, id), readFlags | constants.O_DIRECTORY);
|
|
188
|
+
const pinned = process.platform === 'linux' ? `/proc/self/fd/${entry.fd}` : join(root.path, id);
|
|
189
|
+
const stat = await lstat(join(pinned, 'content'), { bigint: true });
|
|
190
|
+
if (!stat.isFile() || stat.nlink !== 1n || stat.size > BigInt(MAX_FILE_BYTES))
|
|
191
|
+
throw new Error('Unsafe stored artifact');
|
|
192
|
+
bytes += Number(stat.size);
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
if (error.code !== 'ENOENT')
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
await entry?.close();
|
|
200
|
+
}
|
|
201
|
+
if (bytes + incomingBytes > MAX_RUN_BYTES)
|
|
202
|
+
throw new Error('Artifact task byte limit exceeded');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
export async function publishArtifact(directory, runId, source) {
|
|
206
|
+
validateContext(directory, runId);
|
|
207
|
+
const root = await storageDirectory(directory, true);
|
|
208
|
+
let release;
|
|
209
|
+
let pending;
|
|
210
|
+
let staging;
|
|
211
|
+
try {
|
|
212
|
+
release = await acquireLock(root);
|
|
213
|
+
// With the publication lock held, no cooperating writer can own a temporary entry.
|
|
214
|
+
// Completed UUID entries are never evicted, even if their metadata is corrupt.
|
|
215
|
+
for await (const id of entries(root.path)) {
|
|
216
|
+
if (/^\.pending-[0-9a-f-]{36}$/i.test(id))
|
|
217
|
+
await rm(join(root.path, id), { recursive: true, force: true });
|
|
218
|
+
}
|
|
219
|
+
const bytes = await boundedRead(source, MAX_FILE_BYTES);
|
|
220
|
+
// A same-size rewrite inside one timestamp tick leaves the stat snapshot above
|
|
221
|
+
// unchanged, so reread and compare: any mutation that landed before this second
|
|
222
|
+
// read surfaces as differing bytes. Later mutations stay covered by the snapshot.
|
|
223
|
+
const reread = await boundedRead(source, MAX_FILE_BYTES);
|
|
224
|
+
if (!bytes.equals(reread))
|
|
225
|
+
throw new Error('File changed while publishing or reading artifact');
|
|
226
|
+
await checkCapacity(root, bytes.length);
|
|
227
|
+
const metadata = publishedArtifactSchema.parse({ id: randomUUID(), runId, name: basename(source), sourcePath: resolve(source), createdAt: new Date().toISOString(), size: bytes.length, sha256: createHash('sha256').update(bytes).digest('hex') });
|
|
228
|
+
pending = join(root.path, `.pending-${metadata.id}`);
|
|
229
|
+
await root.check();
|
|
230
|
+
await mkdir(pending, { mode: 0o700 });
|
|
231
|
+
staging = await open(pending, readFlags | constants.O_DIRECTORY);
|
|
232
|
+
const pinned = process.platform === 'linux' ? `/proc/self/fd/${staging.fd}` : pending;
|
|
233
|
+
await writeFile(join(pinned, 'content'), bytes, { mode: 0o600, flag: 'wx' });
|
|
234
|
+
await writeFile(join(pinned, 'metadata.json'), JSON.stringify(metadata), { mode: 0o600, flag: 'wx' });
|
|
235
|
+
if (!sameFile(await staging.stat({ bigint: true }), await lstat(pending, { bigint: true })))
|
|
236
|
+
throw new Error('Redirected artifact staging');
|
|
237
|
+
await root.check();
|
|
238
|
+
await rename(pending, join(root.path, metadata.id));
|
|
239
|
+
await root.check();
|
|
240
|
+
return metadata;
|
|
241
|
+
}
|
|
242
|
+
finally {
|
|
243
|
+
try {
|
|
244
|
+
await staging?.close();
|
|
245
|
+
if (pending)
|
|
246
|
+
await rm(pending, { recursive: true, force: true });
|
|
247
|
+
}
|
|
248
|
+
finally {
|
|
249
|
+
try {
|
|
250
|
+
await release?.();
|
|
251
|
+
}
|
|
252
|
+
finally {
|
|
253
|
+
await root.close();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async function readEntry(root, runId, id) {
|
|
259
|
+
if (!z.uuid().safeParse(id).success)
|
|
260
|
+
return null;
|
|
261
|
+
let entry;
|
|
262
|
+
try {
|
|
263
|
+
await root.check();
|
|
264
|
+
entry = await open(join(root.path, id), readFlags | constants.O_DIRECTORY);
|
|
265
|
+
const path = process.platform === 'linux' ? `/proc/self/fd/${entry.fd}` : join(root.path, id);
|
|
266
|
+
const metadata = publishedArtifactSchema.parse(JSON.parse((await boundedRead(join(path, 'metadata.json'), MAX_METADATA_BYTES, true)).toString('utf8')));
|
|
267
|
+
if (metadata.runId !== runId || metadata.id !== id || metadata.size > MAX_FILE_BYTES)
|
|
268
|
+
return null;
|
|
269
|
+
const bytes = await boundedRead(join(path, 'content'), MAX_FILE_BYTES, true);
|
|
270
|
+
if (bytes.length !== metadata.size || createHash('sha256').update(bytes).digest('hex') !== metadata.sha256)
|
|
271
|
+
return null;
|
|
272
|
+
if (!sameFile(await entry.stat({ bigint: true }), await lstat(join(root.path, id), { bigint: true })))
|
|
273
|
+
return null;
|
|
274
|
+
await root.check();
|
|
275
|
+
return { metadata, bytes };
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
finally {
|
|
281
|
+
await entry?.close();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
export async function readArtifact(directory, runId, id) {
|
|
285
|
+
let root;
|
|
286
|
+
try {
|
|
287
|
+
validateContext(directory, runId);
|
|
288
|
+
root = await storageDirectory(directory);
|
|
289
|
+
return await readEntry(root, runId, id);
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
finally {
|
|
295
|
+
await root?.close();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
export async function listArtifacts(directory, runId) {
|
|
299
|
+
let root;
|
|
300
|
+
try {
|
|
301
|
+
validateContext(directory, runId);
|
|
302
|
+
root = await storageDirectory(directory);
|
|
303
|
+
const artifacts = [];
|
|
304
|
+
let bytes = 0;
|
|
305
|
+
for await (const id of entries(root.path)) {
|
|
306
|
+
const entry = await readEntry(root, runId, id);
|
|
307
|
+
if (entry) {
|
|
308
|
+
bytes += entry.metadata.size;
|
|
309
|
+
artifacts.push(entry.metadata);
|
|
310
|
+
if (artifacts.length > MAX_ARTIFACTS || bytes > MAX_RUN_BYTES)
|
|
311
|
+
return [];
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return artifacts.sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
return [];
|
|
318
|
+
}
|
|
319
|
+
finally {
|
|
320
|
+
await root?.close();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/artifacts/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,YAAY,EAAoB,MAAM,SAAS,CAAC;AACpE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAmB,MAAM,kBAAkB,CAAC;AACjH,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAC5E,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,uBAAuB,EAA0B,MAAM,8BAA8B,CAAC;AAE/F,MAAM,cAAc,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AACxC,MAAM,kBAAkB,GAAG,EAAE,GAAG,IAAI,CAAC;AACrC,MAAM,aAAa,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AACvC,MAAM,aAAa,GAAG,EAAE,CAAC;AACzB,MAAM,qBAAqB,GAAG,IAAI,CAAC;AACnC,MAAM,SAAS,GAAG,SAAS,CAAC,QAAQ,GAAG,SAAS,CAAC,UAAU,GAAG,SAAS,CAAC,UAAU,CAAC;AAEnF,SAAS,eAAe,CAAC,SAAiB,EAAE,KAAa;IACvD,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACtB,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,QAAQ,CAAC,SAAS,CAAC,KAAK,GAAG,KAAK,YAAY;QAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;AACjI,CAAC;AACD,MAAM,UAAU,iBAAiB,CAAC,OAAe,EAAE,KAAa;IAC9D,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACtB,yEAAyE;IACzE,2EAA2E;IAC3E,IAAI,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5B,IAAI,CAAC;QAAC,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,yCAAyC,CAAC,CAAC;IACtF,OAAO,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,YAAY,CAAC,CAAC;AAClD,CAAC;AACD,SAAS,QAAQ,CAAC,CAAc,EAAE,CAAc;IAC9C,OAAO,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC;AAC5C,CAAC;AACD,SAAS,YAAY,CAAC,CAAc,EAAE,CAAc;IAClD,OAAO,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK,CAAC;AAC1H,CAAC;AAED,qGAAqG;AACrG,KAAK,UAAU,WAAW,CAAC,IAAY,EAAE,KAAa,EAAE,MAAM,GAAG,KAAK;IACpE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IACzC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACjD,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,MAAM,CAAC,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,KAAK,KAAK,EAAE,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;QACnJ,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACrD,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,OAAO,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;YAC7B,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,GAAG,KAAK,EAAE,KAAK,CAAC,CAAC;YACnF,IAAI,SAAS,KAAK,CAAC;gBAAE,MAAM;YAC3B,KAAK,IAAI,SAAS,CAAC;QACrB,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACpD,IAAI,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,OAAO,CAAC,cAAc,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACjM,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACnC,CAAC;YAAS,CAAC;QAAC,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;IAAC,CAAC;AACnC,CAAC;AAGD;iGACiG;AACjG,KAAK,UAAU,gBAAgB,CAAC,IAAY,EAAE,MAAM,GAAG,KAAK;IAC1D,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,IAA4B,CAAC;IACjC,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;IAClC,IAAI,OAAO,GAAG,IAAI,CAAC;IACnB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC;QAC3D,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;YAC1E,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAC9B,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;YACjG,IAAI,MAAM;gBAAE,MAAM,KAAK,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;oBAAE,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACjH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,SAAS,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC;YACrE,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YAAC,IAAI,GAAG,IAAI,CAAC;QAClC,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC;QACpB,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACrD,MAAM,KAAK,GAAG,KAAK,IAAI,EAAE;YACvB,IAAI,MAAM,QAAQ,CAAC,QAAQ,CAAC,KAAK,QAAQ,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,KAAK,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;QAC5J,CAAC,CAAC;QACF,MAAM,KAAK,EAAE,CAAC;QACd,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,iBAAiB,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC;IAC9H,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAAC,MAAM,IAAI,EAAE,KAAK,EAAE,CAAC;QAAC,MAAM,KAAK,CAAC;IAAC,CAAC;AACvD,CAAC;AAED,KAAK,SAAS,CAAC,CAAC,OAAO,CAAC,IAAY;IAClC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QACjC,IAAI,EAAE,KAAK,GAAG,qBAAqB;YAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;QAChG,MAAM,KAAK,CAAC,IAAI,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;AAC9H,KAAK,UAAU,eAAe,CAAC,IAAe;IAC5C,IAAI,IAA4B,CAAC;IACjC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;QAC9C,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC;QAC3D,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,iBAAiB,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QAChF,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC1H,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE;YAAE,OAAO;QACtC,IAAI,CAAC;YAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YAAC,OAAO;QAAC,CAAC;QAC3C,OAAO,KAAK,EAAE,CAAC;YAAC,IAAK,KAA+B,CAAC,IAAI,KAAK,OAAO;gBAAE,OAAO;QAAC,CAAC;QAChF,uFAAuF;QACvF,wFAAwF;QACxF,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACvD,IAAI,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YAAE,OAAO;QAC9F,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACnB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,iBAAiB,UAAU,EAAE,EAAE,CAAC,CAAC;QACjE,MAAM,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC5B,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC,CAAC,kFAAkF,CAAC,CAAC;YACtF,CAAC;QAAC,MAAM,IAAI,EAAE,KAAK,EAAE,CAAC;IAAC,CAAC;AAClC,CAAC;AACD,KAAK,UAAU,WAAW,CAAC,IAAe;IACxC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,mBAAmB,UAAU,EAAE,EAAE,CAAC,CAAC;IACrE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;IAC9C,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACxC,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACpI,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1D,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;QAC1C,SAAS,CAAC;YACR,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YACnB,IAAI,CAAC;gBACH,sFAAsF;gBACtF,gFAAgF;gBAChF,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,KAA4B,EAAE,EAAE;oBACjF,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;wBAAE,OAAO,KAAK,CAAC;oBAC1C,MAAM,KAAK,CAAC;gBACd,CAAC,CAAC,CAAC;gBACH,IAAI,CAAC,MAAM,EAAE,CAAC;oBACZ,MAAM,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;oBAC9B,OAAO,KAAK,IAAI,EAAE;wBAChB,IAAI,QAAQ,CAAC,QAAQ,EAAE,MAAM,KAAK,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;4BAAE,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;oBAChH,CAAC,CAAC;gBACJ,CAAC;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAE,KAA+B,CAAC,IAAI,IAAI,EAAE,CAAC;oBAAE,MAAM,KAAK,CAAC;YAClG,CAAC;YACD,IAAI,WAAW,CAAC,GAAG,EAAE,IAAI,QAAQ;gBAAE,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;YACrH,MAAM,eAAe,CAAC,IAAI,CAAC,CAAC;YAC5B,MAAM,KAAK,CAAC,EAAE,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;YAAS,CAAC;QAAC,MAAM,EAAE,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAAC,CAAC;AACtE,CAAC;AACD,KAAK,UAAU,aAAa,CAAC,IAAe,EAAE,aAAqB;IACjE,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,KAAK,EAAE,MAAM,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1C,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,OAAO,IAAI,CAAC,4BAA4B,CAAC,IAAI,CAAC,EAAE,CAAC;YAAE,SAAS;QACxF,IAAI,EAAE,KAAK,IAAI,aAAa;YAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;QAC/E,IAAI,KAA6B,CAAC;QAClC,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC;YAC3E,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,iBAAiB,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAChG,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YACpE,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,KAAK,KAAK,EAAE,IAAI,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,cAAc,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;YACzH,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAAC,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,KAAK,CAAC;QAAC,CAAC;gBAChF,CAAC;YAAC,MAAM,KAAK,EAAE,KAAK,EAAE,CAAC;QAAC,CAAC;QACjC,IAAI,KAAK,GAAG,aAAa,GAAG,aAAa;YAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IAClG,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,SAAiB,EAAE,KAAa,EAAE,MAAc;IACpF,eAAe,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IACrD,IAAI,OAA0C,CAAC;IAC/C,IAAI,OAA2B,CAAC;IAChC,IAAI,OAA+B,CAAC;IACpC,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC;QAClC,mFAAmF;QACnF,+EAA+E;QAC/E,IAAI,KAAK,EAAE,MAAM,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,IAAI,4BAA4B,CAAC,IAAI,CAAC,EAAE,CAAC;gBAAE,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7G,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;QACxD,+EAA+E;QAC/E,gFAAgF;QAChF,kFAAkF;QAClF,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;QACzD,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QAChG,MAAM,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QACxC,MAAM,QAAQ,GAAG,uBAAuB,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACpP,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;QACrD,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACnB,MAAM,KAAK,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACtC,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC;QACjE,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,iBAAiB,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;QACtF,MAAM,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7E,MAAM,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACtG,IAAI,CAAC,QAAQ,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;QAC5I,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACnB,MAAM,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;QACpD,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACnB,OAAO,QAAQ,CAAC;IAClB,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YAAC,MAAM,OAAO,EAAE,KAAK,EAAE,CAAC;YAAC,IAAI,OAAO;gBAAE,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAAC,CAAC;gBACzF,CAAC;YAAC,IAAI,CAAC;gBAAC,MAAM,OAAO,EAAE,EAAE,CAAC;YAAC,CAAC;oBAAS,CAAC;gBAAC,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YAAC,CAAC;QAAC,CAAC;IACxE,CAAC;AACH,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,IAAe,EAAE,KAAa,EAAE,EAAU;IACjE,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IACjD,IAAI,KAA6B,CAAC;IAClC,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACnB,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC;QAC3E,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,iBAAiB,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC9F,MAAM,QAAQ,GAAG,uBAAuB,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,eAAe,CAAC,EAAE,kBAAkB,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACxJ,IAAI,QAAQ,CAAC,KAAK,KAAK,KAAK,IAAI,QAAQ,CAAC,EAAE,KAAK,EAAE,IAAI,QAAQ,CAAC,IAAI,GAAG,cAAc;YAAE,OAAO,IAAI,CAAC;QAClG,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,cAAc,EAAE,IAAI,CAAC,CAAC;QAC7E,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ,CAAC,IAAI,IAAI,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,QAAQ,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QACxH,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QACnH,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACnB,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;YAAS,CAAC;QAAC,MAAM,KAAK,EAAE,KAAK,EAAE,CAAC;IAAC,CAAC;AAC5D,CAAC;AACD,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,SAAiB,EAAE,KAAa,EAAE,EAAU;IAC7E,IAAI,IAA2B,CAAC;IAChC,IAAI,CAAC;QACH,eAAe,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAClC,IAAI,GAAG,MAAM,gBAAgB,CAAC,SAAS,CAAC,CAAC;QACzC,OAAO,MAAM,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;YAAS,CAAC;QAAC,MAAM,IAAI,EAAE,KAAK,EAAE,CAAC;IAAC,CAAC;AAC3D,CAAC;AACD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,SAAiB,EAAE,KAAa;IAClE,IAAI,IAA2B,CAAC;IAChC,IAAI,CAAC;QACH,eAAe,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAClC,IAAI,GAAG,MAAM,gBAAgB,CAAC,SAAS,CAAC,CAAC;QACzC,MAAM,SAAS,GAAwB,EAAE,CAAC;QAC1C,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,KAAK,EAAE,MAAM,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;YAC/C,IAAI,KAAK,EAAE,CAAC;gBACV,KAAK,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAC7B,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBAC/B,IAAI,SAAS,CAAC,MAAM,GAAG,aAAa,IAAI,KAAK,GAAG,aAAa;oBAAE,OAAO,EAAE,CAAC;YAC3E,CAAC;QACH,CAAC;QACD,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACtG,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC;IAAC,CAAC;YAAS,CAAC;QAAC,MAAM,IAAI,EAAE,KAAK,EAAE,CAAC;IAAC,CAAC;AACzD,CAAC"}
|
|
@@ -15,11 +15,16 @@ export interface ProjectAutomationHandle {
|
|
|
15
15
|
launch?: AutomationLauncher;
|
|
16
16
|
onChange?: (automationId: string, revision: number) => void;
|
|
17
17
|
}
|
|
18
|
+
/** A different process currently owns this project's poll lock. */
|
|
19
|
+
export declare class LeaseHeldError extends Error {
|
|
20
|
+
constructor();
|
|
21
|
+
}
|
|
18
22
|
export declare class ProjectAutomationScheduler {
|
|
19
23
|
private readonly handle;
|
|
20
24
|
constructor(handle: ProjectAutomationHandle);
|
|
21
25
|
check(definition: AutomationDefinition, mode?: 'preview' | 'execute'): Promise<GithubPollResult>;
|
|
22
26
|
private launch;
|
|
27
|
+
private recordSkip;
|
|
23
28
|
private recordFailure;
|
|
24
29
|
}
|
|
25
30
|
export interface WorkspaceAutomationSchedulerOptions {
|
|
@@ -33,6 +38,7 @@ export declare class WorkspaceAutomationScheduler {
|
|
|
33
38
|
private timer?;
|
|
34
39
|
private stopped;
|
|
35
40
|
private scheduleGeneration;
|
|
41
|
+
private readonly retryAfter;
|
|
36
42
|
constructor(options: WorkspaceAutomationSchedulerOptions);
|
|
37
43
|
start(): Promise<void>;
|
|
38
44
|
reschedule(): Promise<void>;
|
|
@@ -9,6 +9,10 @@ class GithubRequestArbiter {
|
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
const githubRequests = new GithubRequestArbiter();
|
|
12
|
+
/** A different process currently owns this project's poll lock. */
|
|
13
|
+
export class LeaseHeldError extends Error {
|
|
14
|
+
constructor() { super('automation polling lease is held by another process'); }
|
|
15
|
+
}
|
|
12
16
|
export class ProjectAutomationScheduler {
|
|
13
17
|
handle;
|
|
14
18
|
constructor(handle) {
|
|
@@ -19,12 +23,13 @@ export class ProjectAutomationScheduler {
|
|
|
19
23
|
if (detectionOnly)
|
|
20
24
|
mode = 'preview';
|
|
21
25
|
const { store } = this.handle;
|
|
22
|
-
const lease = store.acquireLease();
|
|
23
|
-
if (!lease)
|
|
24
|
-
throw new Error('automation polling lease is held by another process');
|
|
25
26
|
const started = Date.now();
|
|
26
27
|
let completion;
|
|
28
|
+
let lease;
|
|
27
29
|
try {
|
|
30
|
+
lease = store.acquireLease();
|
|
31
|
+
if (!lease)
|
|
32
|
+
throw new LeaseHeldError();
|
|
28
33
|
const state = store.state(definition.id) ?? {};
|
|
29
34
|
if (state.backoffUntil && Date.parse(state.backoffUntil) > Date.now()) {
|
|
30
35
|
throw new Error(`automation is backed off until ${state.backoffUntil}`);
|
|
@@ -76,39 +81,53 @@ export class ProjectAutomationScheduler {
|
|
|
76
81
|
return { ...result, candidates: eligible };
|
|
77
82
|
}
|
|
78
83
|
catch (error) {
|
|
79
|
-
if (
|
|
80
|
-
this.
|
|
84
|
+
if (error instanceof LeaseHeldError)
|
|
85
|
+
await this.recordSkip(definition, error);
|
|
86
|
+
else if (mode === 'execute')
|
|
87
|
+
await this.recordFailure(definition, error);
|
|
81
88
|
else
|
|
82
|
-
store.appendLog({ automationId: definition.id, revision: definition.revision, result: 'error', reason: error instanceof Error ? error.message : String(error) });
|
|
89
|
+
await store.appendLog({ automationId: definition.id, revision: definition.revision, result: 'error', reason: error instanceof Error ? error.message : String(error) });
|
|
83
90
|
throw error;
|
|
84
91
|
}
|
|
85
92
|
finally {
|
|
86
|
-
if (completion)
|
|
87
|
-
store.appendLog({ automationId: definition.id, revision: definition.revision, ...completion, durationMs: Date.now() - started });
|
|
88
93
|
try {
|
|
89
|
-
|
|
94
|
+
if (completion)
|
|
95
|
+
await store.appendLog({ automationId: definition.id, revision: definition.revision, ...completion, durationMs: Date.now() - started });
|
|
96
|
+
if (lease) {
|
|
97
|
+
try {
|
|
98
|
+
await store.maybeCompact();
|
|
99
|
+
}
|
|
100
|
+
catch { /* append-only state remains readable; next check retries */ }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
lease?.release();
|
|
90
105
|
}
|
|
91
|
-
catch { /* append-only state remains readable; next check retries */ }
|
|
92
|
-
lease.release();
|
|
93
106
|
}
|
|
94
107
|
}
|
|
95
108
|
async launch(definition, candidate) {
|
|
96
109
|
const receipt = this.handle.store.reserveReceipt({ automationId: definition.id, revision: definition.revision, eventId: candidate.eventId, candidate });
|
|
97
110
|
if (!receipt) {
|
|
98
|
-
this.handle.store.appendLog({ automationId: definition.id, revision: definition.revision, event: candidate.event, result: 'duplicate', reason: 'A durable receipt already exists for this automation and event.', githubNumber: candidate.number, githubTitle: candidate.title, githubUrl: candidate.url });
|
|
111
|
+
await this.handle.store.appendLog({ automationId: definition.id, revision: definition.revision, event: candidate.event, result: 'duplicate', reason: 'A durable receipt already exists for this automation and event.', githubNumber: candidate.number, githubTitle: candidate.title, githubUrl: candidate.url });
|
|
99
112
|
return;
|
|
100
113
|
}
|
|
101
114
|
try {
|
|
102
115
|
const launched = await this.handle.launch(definition, candidate, receipt.receiptId);
|
|
103
116
|
this.handle.store.appendReceipt({ ...receipt, status: 'launched', runId: launched.runId, updatedAt: new Date().toISOString() });
|
|
104
|
-
this.handle.store.appendLog({ automationId: definition.id, revision: definition.revision, event: candidate.event, result: 'launched', receiptId: receipt.receiptId, runId: launched.runId, githubNumber: candidate.number, githubTitle: candidate.title, githubUrl: candidate.url });
|
|
117
|
+
await this.handle.store.appendLog({ automationId: definition.id, revision: definition.revision, event: candidate.event, result: 'launched', receiptId: receipt.receiptId, runId: launched.runId, githubNumber: candidate.number, githubTitle: candidate.title, githubUrl: candidate.url });
|
|
105
118
|
}
|
|
106
119
|
catch (error) {
|
|
107
120
|
this.handle.store.appendReceipt({ ...receipt, status: 'launch-error', error: error instanceof Error ? error.message : String(error), updatedAt: new Date().toISOString() });
|
|
108
121
|
throw error;
|
|
109
122
|
}
|
|
110
123
|
}
|
|
111
|
-
|
|
124
|
+
async recordSkip(definition, error) {
|
|
125
|
+
const { store } = this.handle;
|
|
126
|
+
await store.appendLog({ automationId: definition.id, revision: definition.revision, result: 'skipped', reason: error.message });
|
|
127
|
+
// The lease owner may be writing state. The workspace retry floor handles our next attempt.
|
|
128
|
+
this.handle.onChange?.(definition.id, definition.revision);
|
|
129
|
+
}
|
|
130
|
+
async recordFailure(definition, error) {
|
|
112
131
|
const state = this.handle.store.state(definition.id) ?? {};
|
|
113
132
|
const failures = (state.consecutiveFailures ?? 0) + 1;
|
|
114
133
|
const delay = Math.min(6 * 60 * 60_000, 60_000 * 2 ** (failures - 1));
|
|
@@ -118,7 +137,7 @@ export class ProjectAutomationScheduler {
|
|
|
118
137
|
backoffUntil: new Date(Date.now() + delay).toISOString(),
|
|
119
138
|
nextCheckAt: new Date(Date.now() + delay).toISOString(),
|
|
120
139
|
});
|
|
121
|
-
this.handle.store.appendLog({ automationId: definition.id, revision: definition.revision, result: 'error', reason: error instanceof Error ? error.message : String(error) });
|
|
140
|
+
await this.handle.store.appendLog({ automationId: definition.id, revision: definition.revision, result: 'error', reason: error instanceof Error ? error.message : String(error) });
|
|
122
141
|
this.handle.onChange?.(definition.id, definition.revision);
|
|
123
142
|
}
|
|
124
143
|
}
|
|
@@ -131,12 +150,14 @@ function laterCursor(current, observed) {
|
|
|
131
150
|
|| observed.tieBreaker.localeCompare(current.tieBreaker ?? '');
|
|
132
151
|
return order > 0 ? observed : current;
|
|
133
152
|
}
|
|
153
|
+
const MIN_RETRY_MS = 60_000;
|
|
134
154
|
/** One workspace timer, created only while at least one enabled definition exists. */
|
|
135
155
|
export class WorkspaceAutomationScheduler {
|
|
136
156
|
options;
|
|
137
157
|
timer;
|
|
138
158
|
stopped = true;
|
|
139
159
|
scheduleGeneration = 0;
|
|
160
|
+
retryAfter = new Map();
|
|
140
161
|
constructor(options) {
|
|
141
162
|
this.options = options;
|
|
142
163
|
}
|
|
@@ -168,6 +189,7 @@ export class WorkspaceAutomationScheduler {
|
|
|
168
189
|
if (this.stopped)
|
|
169
190
|
return;
|
|
170
191
|
const due = [];
|
|
192
|
+
const live = new Set();
|
|
171
193
|
for (const projectId of this.options.coordinator.enabledProjectIds()) {
|
|
172
194
|
const store = this.options.coordinator.store(projectId);
|
|
173
195
|
if (!store)
|
|
@@ -176,16 +198,22 @@ export class WorkspaceAutomationScheduler {
|
|
|
176
198
|
if (!handle)
|
|
177
199
|
continue;
|
|
178
200
|
for (const definition of store.list().filter((item) => item.enabled)) {
|
|
179
|
-
|
|
201
|
+
const key = `${projectId}:${definition.id}`;
|
|
202
|
+
live.add(key);
|
|
203
|
+
const at = Date.parse(store.state(definition.id)?.nextCheckAt ?? new Date().toISOString());
|
|
204
|
+
due.push({ key, at: Math.max(at, this.retryAfter.get(key) ?? 0), retryAfterMs: Math.max(definition.intervalSeconds * 1_000, MIN_RETRY_MS), definition, scheduler: new ProjectAutomationScheduler(handle) });
|
|
180
205
|
}
|
|
181
206
|
}
|
|
207
|
+
for (const key of this.retryAfter.keys())
|
|
208
|
+
if (!live.has(key))
|
|
209
|
+
this.retryAfter.delete(key);
|
|
182
210
|
if (!due.length)
|
|
183
211
|
return;
|
|
184
212
|
due.sort((a, b) => a.at - b.at);
|
|
185
213
|
const next = due[0];
|
|
186
214
|
this.timer = setTimeout(() => {
|
|
187
215
|
this.timer = undefined;
|
|
188
|
-
void next.scheduler.check(next.definition).
|
|
216
|
+
void next.scheduler.check(next.definition).then(() => { this.retryAfter.delete(next.key); }, () => { this.retryAfter.set(next.key, (this.options.now?.() ?? Date.now()) + next.retryAfterMs); }).finally(() => this.schedule());
|
|
189
217
|
}, Math.max(0, next.at - (this.options.now?.() ?? Date.now())));
|
|
190
218
|
}
|
|
191
219
|
}
|