lagora-cli 1.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 (52) hide show
  1. package/README.md +138 -0
  2. package/dist/help.txt +70 -0
  3. package/dist/lagora.js +342 -0
  4. package/dist/report-help.txt +5 -0
  5. package/dist/scripts/agora_playground_harness.py +263 -0
  6. package/dist/scripts/announce.js +41 -0
  7. package/dist/scripts/check-kernel-submission.py +90 -0
  8. package/dist/scripts/chunk-2EAJVB5D.js +100 -0
  9. package/dist/scripts/chunk-2KTLCUFI.js +29 -0
  10. package/dist/scripts/chunk-AZ3EEBVD.js +137 -0
  11. package/dist/scripts/chunk-NBJMYAOA.js +2128 -0
  12. package/dist/scripts/chunk-NCJMUBTG.js +125 -0
  13. package/dist/scripts/chunk-QJPQHKIO.js +23 -0
  14. package/dist/scripts/chunk-RIR5KGHC.js +33 -0
  15. package/dist/scripts/chunk-TJZVQYBL.js +8 -0
  16. package/dist/scripts/chunk-UHJXD4TG.js +18 -0
  17. package/dist/scripts/chunk-UQ6I6VTY.js +117 -0
  18. package/dist/scripts/cli-auth.js +348 -0
  19. package/dist/scripts/cli-config-IA7EOSYD.js +7 -0
  20. package/dist/scripts/install-skill.js +199 -0
  21. package/dist/scripts/issue-local-client-DZUXZOKY.js +22 -0
  22. package/dist/scripts/issue-search.js +1823 -0
  23. package/dist/scripts/issue.js +386 -0
  24. package/dist/scripts/keycloak-provision.js +986 -0
  25. package/dist/scripts/legato-fsim-runner.py +126 -0
  26. package/dist/scripts/legato-lowering-runner.py +156 -0
  27. package/dist/scripts/legato_runner_annotations.py +235 -0
  28. package/dist/scripts/legato_runner_env.py +91 -0
  29. package/dist/scripts/legato_runner_launchers.py +287 -0
  30. package/dist/scripts/legato_runner_script_wrapper.py +193 -0
  31. package/dist/scripts/notifications-EU43SIEV.js +624 -0
  32. package/dist/scripts/playground.js +408 -0
  33. package/dist/scripts/report-bundle-sync-3U7QTP4Z.js +215 -0
  34. package/dist/scripts/report.js +104 -0
  35. package/dist/scripts/resolve-sdk-package-version.py +151 -0
  36. package/dist/scripts/sdk-runtime-JE6H2PB2.js +992 -0
  37. package/dist/scripts/sdk-runtime-kubernetes-job-KOWL4ITV.js +479 -0
  38. package/dist/scripts/sdk-runtime-smoke.py +168 -0
  39. package/dist/scripts/sdk.js +256 -0
  40. package/dist/scripts/site-feedback-CAPE5MPX.js +136 -0
  41. package/dist/scripts/site-feedback-rate-limit-5BU2WSFE.js +86 -0
  42. package/dist/scripts/site-feedback.js +117 -0
  43. package/dist/scripts/storage-234FBH54.js +67 -0
  44. package/dist/scripts/submit-issue.sh +489 -0
  45. package/dist/scripts/verification-3QCY66QW.js +772 -0
  46. package/dist/scripts/verify-issue.js +144 -0
  47. package/dist/skills/legato-agora-cli/SKILL.md +556 -0
  48. package/dist/skills/legato-agora-cli/agents/openai.yaml +7 -0
  49. package/dist/skills/legato-agora-cli/reference/kernel-with-golden.py +84 -0
  50. package/dist/skills/legato-site-feedback/SKILL.md +49 -0
  51. package/dist/skills/legato-site-feedback/agents/openai.yaml +7 -0
  52. package/package.json +16 -0
@@ -0,0 +1,479 @@
1
+ import {
2
+ sdkPackageIndexEnv
3
+ } from "./chunk-2KTLCUFI.js";
4
+ import {
5
+ SDK_PREPARE_SOURCE_PATHS,
6
+ sdkPrepareCacheEnv,
7
+ sdkPrepareCacheFingerprint,
8
+ sdkPrepareCacheStatePath
9
+ } from "./chunk-UQ6I6VTY.js";
10
+
11
+ // lib/server/sdk-runtime-kubernetes-job.ts
12
+ import { randomUUID } from "node:crypto";
13
+
14
+ // lib/server/kubernetes-client.ts
15
+ import { readFile } from "node:fs/promises";
16
+ import https from "node:https";
17
+ import path from "node:path";
18
+ var SERVICE_ACCOUNT_ROOT = "/var/run/secrets/kubernetes.io/serviceaccount";
19
+ async function inClusterKubernetesClient() {
20
+ const endpoint = kubernetesApiEndpointFromEnv(process.env);
21
+ const namespace = (await readFile(path.join(SERVICE_ACCOUNT_ROOT, "namespace"), "utf8")).trim();
22
+ const token = (await readFile(path.join(SERVICE_ACCOUNT_ROOT, "token"), "utf8")).trim();
23
+ const ca = await readFile(path.join(SERVICE_ACCOUNT_ROOT, "ca.crt"));
24
+ return {
25
+ namespace,
26
+ request: async (input) => await kubernetesRequest({ ...endpoint, token, ca, ...input })
27
+ };
28
+ }
29
+ function kubernetesApiEndpointFromEnv(env) {
30
+ return {
31
+ host: env.AGORA_KUBERNETES_API_HOST ?? "kubernetes.default.svc",
32
+ port: env.KUBERNETES_SERVICE_PORT_HTTPS ?? env.KUBERNETES_SERVICE_PORT ?? "443"
33
+ };
34
+ }
35
+ async function podLogs(client, jobName, container, tailLines = 4e3) {
36
+ const pods = await listJobPods(client, jobName);
37
+ if (typeof pods === "string") return pods;
38
+ const name = readString(readRecord(pods, "metadata"), "name");
39
+ if (!name) return "Job pod was not found.";
40
+ const logs = await client.request({
41
+ method: "GET",
42
+ path: `/api/v1/namespaces/${client.namespace}/pods/${name}/log?container=${encodeURIComponent(container)}&tailLines=${tailLines}`
43
+ });
44
+ if (logs.statusCode >= 400) return `Failed to read job logs: HTTP ${logs.statusCode}`;
45
+ return logs.body;
46
+ }
47
+ async function podFailureReason(client, jobName) {
48
+ const pod = await listJobPods(client, jobName);
49
+ if (typeof pod === "string") return pod;
50
+ return podFailureDetails(pod);
51
+ }
52
+ async function listJobPods(client, jobName) {
53
+ const pods = await client.request({
54
+ method: "GET",
55
+ path: `/api/v1/namespaces/${client.namespace}/pods?labelSelector=job-name%3D${encodeURIComponent(jobName)}`
56
+ });
57
+ if (pods.statusCode >= 400) return `Failed to list job pods: HTTP ${pods.statusCode}`;
58
+ const items = readArray(readRecord(JSON.parse(pods.body), void 0), "items");
59
+ const first = items[0];
60
+ return isRecord(first) ? first : "Job pod was not found.";
61
+ }
62
+ function podFailureDetails(pod) {
63
+ const status = readRecord(pod, "status");
64
+ const lines = [];
65
+ for (const key of ["phase", "reason", "message"]) {
66
+ const value = readString(status, key);
67
+ if (value) lines.push(`pod ${key}: ${value}`);
68
+ }
69
+ for (const item of readArray(status, "containerStatuses")) {
70
+ if (!isRecord(item)) continue;
71
+ const name = readString(item, "name") ?? "container";
72
+ lines.push(...containerStateDetails(name, readRecord(item, "state")));
73
+ lines.push(...containerStateDetails(`${name} last state`, readRecord(item, "lastState")));
74
+ }
75
+ return lines.join("\n") || "Pod failed without a Kubernetes status reason.";
76
+ }
77
+ function containerStateDetails(name, state) {
78
+ for (const phase of ["terminated", "waiting"]) {
79
+ const detail = readRecord(state, phase);
80
+ if (Object.keys(detail).length > 0) return [`${name} ${phase}: ${formatState(detail)}`];
81
+ }
82
+ return Object.keys(readRecord(state, "running")).length > 0 ? [`${name} running`] : [];
83
+ }
84
+ function formatState(state) {
85
+ const parts = [
86
+ readString(state, "reason"),
87
+ readNumberValue(state, "exitCode"),
88
+ readNumberValue(state, "signal"),
89
+ readString(state, "message")
90
+ ].filter(Boolean);
91
+ return parts.length > 0 ? parts.join(", ") : "no reason reported";
92
+ }
93
+ function kubernetesRequest(input) {
94
+ const body = input.body === void 0 ? void 0 : JSON.stringify(input.body);
95
+ return new Promise((resolve, reject) => {
96
+ const request = https.request({
97
+ host: input.host,
98
+ port: input.port,
99
+ path: input.path,
100
+ method: input.method,
101
+ ca: input.ca,
102
+ headers: {
103
+ Authorization: `Bearer ${input.token}`,
104
+ ...body === void 0 ? {} : { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) }
105
+ }
106
+ }, (response) => {
107
+ response.setEncoding("utf8");
108
+ let data = "";
109
+ response.on("data", (chunk) => {
110
+ data += chunk;
111
+ });
112
+ response.on("end", () => {
113
+ resolve({ statusCode: response.statusCode ?? 0, body: data });
114
+ });
115
+ });
116
+ request.on("error", reject);
117
+ if (body !== void 0) request.write(body);
118
+ request.end();
119
+ });
120
+ }
121
+ function readRecord(value, key) {
122
+ const candidate = key === void 0 ? value : isRecord(value) ? value[key] : void 0;
123
+ return isRecord(candidate) ? candidate : {};
124
+ }
125
+ function readArray(record, key) {
126
+ const value = record[key];
127
+ return Array.isArray(value) ? value : [];
128
+ }
129
+ function readString(record, key) {
130
+ const value = record[key];
131
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
132
+ }
133
+ function readNumber(record, key) {
134
+ const value = record[key];
135
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
136
+ }
137
+ function readNumberValue(record, key) {
138
+ const value = record[key];
139
+ return typeof value === "number" && Number.isFinite(value) ? `${key} ${value}` : void 0;
140
+ }
141
+ function isRecord(value) {
142
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
143
+ }
144
+
145
+ // lib/server/sdk-runtime-job-script.ts
146
+ import path2 from "node:path";
147
+ function sdkPrepareShellScript(input) {
148
+ const parent = path2.dirname(input.settings.preparedRoot);
149
+ const progressParent = path2.dirname(input.progressPath);
150
+ const liveLogParent = path2.dirname(input.liveLogPath);
151
+ const resultParent = path2.dirname(input.resultPath);
152
+ const cacheStatePath = sdkPrepareCacheStatePath(input.settings);
153
+ const cacheEnv = sdkPrepareCacheEnv(input.settings);
154
+ const cacheFingerprint = sdkPrepareCacheFingerprint(input.settings);
155
+ return [
156
+ "set -eu",
157
+ `mkdir -p ${shellQuote(parent)} ${shellQuote(progressParent)} ${shellQuote(liveLogParent)} ${shellQuote(resultParent)} ${shellQuote(path2.dirname(cacheStatePath))}`,
158
+ `progress_file=${shellQuote(input.progressPath)}`,
159
+ `live_log_file=${shellQuote(input.liveLogPath)}`,
160
+ `result_file=${shellQuote(input.resultPath)}`,
161
+ `cache_state_file=${shellQuote(cacheStatePath)}`,
162
+ `cache_fingerprint=${shellQuote(cacheFingerprint)}`,
163
+ `run_id=${shellQuote(input.runId)}`,
164
+ `runtime_root=${shellQuote(input.settings.runtimeRoot)}`,
165
+ `prepared_root=${shellQuote(input.settings.preparedRoot)}`,
166
+ `status_root=${shellQuote(input.statusRoot ?? input.settings.preparedRoot)}`,
167
+ `python_path=${shellQuote(input.settings.pythonPath)}`,
168
+ `repo_url=${shellQuote(input.settings.repoUrl)}`,
169
+ `sdk_ref=${shellQuote(input.settings.branch)}`,
170
+ `smoke_script=${shellQuote(process.env.AGORA_SDK_PREPARE_SMOKE_SCRIPT ?? "/app/scripts/sdk-runtime-smoke.py")}`,
171
+ `smoke_stages=${shellQuote(process.env.AGORA_SDK_PREPARE_SMOKE_STAGES ?? "MLIR,CORE_IR")}`,
172
+ `export PATH=${shellQuote(cacheEnv.PATH)}`,
173
+ `export LD_LIBRARY_PATH=${shellQuote(cacheEnv.LD_LIBRARY_PATH)}`,
174
+ `export CC=${shellQuote(cacheEnv.CC)}`,
175
+ `export CXX=${shellQuote(cacheEnv.CXX)}`,
176
+ `export CMAKE_PREFIX_PATH=${shellQuote(cacheEnv.CMAKE_PREFIX_PATH)}`,
177
+ `export LLVM_DIR=${shellQuote(cacheEnv.LLVM_DIR)}`,
178
+ `export MLIR_DIR=${shellQuote(cacheEnv.MLIR_DIR)}`,
179
+ `export OpenMP_ROOT=${shellQuote(cacheEnv.OpenMP_ROOT)}`,
180
+ `export UV_CACHE_DIR=${shellQuote(cacheEnv.UV_CACHE_DIR)}`,
181
+ `export UV_PYTHON_INSTALL_DIR=${shellQuote(cacheEnv.UV_PYTHON_INSTALL_DIR)}`,
182
+ `export CCACHE_DIR=${shellQuote(cacheEnv.CCACHE_DIR)}`,
183
+ `export CCACHE_BASEDIR=${shellQuote(cacheEnv.CCACHE_BASEDIR)}`,
184
+ `export UV_LINK_MODE=${shellQuote(cacheEnv.UV_LINK_MODE)}`,
185
+ `export UV_LOCK_TIMEOUT=${shellQuote(cacheEnv.UV_LOCK_TIMEOUT)}`,
186
+ "current_step=start",
187
+ "current_message='Preparing SDK source worktree'",
188
+ "started_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)",
189
+ 'export AGORA_STARTED_AT="$started_at"',
190
+ `progress() { current_step=$1; current_message=$2; printf '{"at":"%s","runId":"%s","step":"%s","message":"%s"}\\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$run_id" "$1" "$2" >> "$progress_file"; }`,
191
+ `write_runtime_status() { AGORA_STATUS=$1 AGORA_COMPLETED_AT=$2 AGORA_MESSAGE=$3 AGORA_COMMIT_SHA=\${commit_sha:-} AGORA_REPO_URL="$repo_url" AGORA_BRANCH="$sdk_ref" AGORA_RUNTIME_ROOT="$runtime_root" AGORA_PREPARED_ROOT="$prepared_root" AGORA_STATUS_ROOT="$status_root" AGORA_PYTHON_PATH="$python_path" node <<'NODE'
192
+ const fs = require('node:fs');
193
+ const path = require('node:path');
194
+ const status = {
195
+ status: process.env.AGORA_STATUS,
196
+ repoUrl: process.env.AGORA_REPO_URL,
197
+ branch: process.env.AGORA_BRANCH,
198
+ runtimeRoot: path.resolve(process.env.AGORA_RUNTIME_ROOT),
199
+ preparedRoot: path.resolve(process.env.AGORA_PREPARED_ROOT),
200
+ pythonPath: process.env.AGORA_PYTHON_PATH,
201
+ startedAt: process.env.AGORA_STARTED_AT,
202
+ completedAt: process.env.AGORA_COMPLETED_AT,
203
+ message: process.env.AGORA_MESSAGE,
204
+ };
205
+ if (process.env.AGORA_COMMIT_SHA) status.commitSha = process.env.AGORA_COMMIT_SHA;
206
+ for (const root of new Set([process.env.AGORA_PREPARED_ROOT, process.env.AGORA_STATUS_ROOT])) {
207
+ fs.mkdirSync(root, { recursive: true });
208
+ fs.writeFileSync(path.join(root, 'runtime.json'), \`\${JSON.stringify(status, null, 2)}\\n\`);
209
+ }
210
+ NODE
211
+ }`,
212
+ 'run_smoke_probe() { PYTHONPATH="$prepared_root/legato_aten_lib${PYTHONPATH:+:$PYTHONPATH}" "$python_path" "$smoke_script" --repo-root "$prepared_root" --output-root "$prepared_root/.agora/smoke" --stages "$smoke_stages"; }',
213
+ `finish() { code=$?; trap - EXIT; completed_at=$(date -u +%Y-%m-%dT%H:%M:%SZ); if [ "$code" -ne 0 ]; then message=$(printf 'SDK prepare job failed during %s' "$current_step"); progress failed "$message"; write_runtime_status failed "$completed_at" "$message"; printf '{"status":"failed","exitCode":%s,"completedAt":"%s","message":"%s"}\\n' "$code" "$completed_at" "$message" > "$result_file"; else write_runtime_status ready "$completed_at" "SDK prepare job completed"; printf '{"status":"ready","exitCode":0,"completedAt":"%s","message":"SDK prepare job completed"}\\n' "$completed_at" > "$result_file"; fi; exit "$code"; }`,
214
+ "trap finish EXIT",
215
+ `printf '\\n===== SDK prepare run %s started at %s =====\\n' "$run_id" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$live_log_file"`,
216
+ 'progress start "Preparing SDK source worktree"',
217
+ 'exec >> "$live_log_file" 2>&1',
218
+ 'progress git-auth "Configuring GitHub authentication"',
219
+ `printf '\\n[%s] Configuring GitHub authentication\\n' "$(date -u +%H:%M:%S)"`,
220
+ `if [ -n "\${AGORA_GITHUB_TOKEN:-}" ]; then git config --global http.https://github.com/.extraheader "AUTHORIZATION: basic $(printf 'x-access-token:%s' "$AGORA_GITHUB_TOKEN" | base64 | tr -d '\\n')"; fi`,
221
+ "git config --global url.https://github.com/.insteadOf git@github.com:",
222
+ 'progress sync-source "Syncing SDK source"',
223
+ `printf '\\n[%s] Syncing SDK source %s#%s\\n' "$(date -u +%H:%M:%S)" ${shellQuote(input.settings.repoUrl)} ${shellQuote(input.settings.branch)}`,
224
+ `if [ -d ${shellQuote(path2.join(input.settings.preparedRoot, ".git"))} ]; then`,
225
+ ` git -C ${shellQuote(input.settings.preparedRoot)} remote set-url origin ${shellQuote(input.settings.repoUrl)}`,
226
+ ` git -C ${shellQuote(input.settings.preparedRoot)} reset --hard`,
227
+ ` git -C ${shellQuote(input.settings.preparedRoot)} clean -ffd -e .venv/`,
228
+ "else",
229
+ ` if [ -d ${shellQuote(input.settings.preparedRoot)} ] && [ "$(find ${shellQuote(input.settings.preparedRoot)} -mindepth 1 -maxdepth 1 | head -n 1)" ]; then backup=$(printf '%s.pre-sdk-job-%s' ${shellQuote(input.settings.preparedRoot)} "$(date +%s)"); mv ${shellQuote(input.settings.preparedRoot)} "$backup"; fi`,
230
+ ` git clone --no-checkout ${shellQuote(input.settings.repoUrl)} ${shellQuote(input.settings.preparedRoot)}`,
231
+ "fi",
232
+ `git -C ${shellQuote(input.settings.preparedRoot)} fetch origin ${shellQuote(input.settings.branch)}`,
233
+ `git -C ${shellQuote(input.settings.preparedRoot)} checkout --force --detach FETCH_HEAD`,
234
+ `git -C ${shellQuote(input.settings.preparedRoot)} reset --hard FETCH_HEAD`,
235
+ `cd ${shellQuote(input.settings.preparedRoot)}`,
236
+ // A prepare killed mid-clone -- a pod restart during a deploy is enough --
237
+ // leaves a submodule whose HEAD cannot be resolved ("Unable to find current
238
+ // revision in submodule path"), and every later prepare dies on the same
239
+ // wreckage. It outlives the pod on the shared volume, so without this repair
240
+ // one interrupted prepare blocks every SDK update from then on.
241
+ `printf '\\n[%s] Syncing SDK submodules\\n' "$(date -u +%H:%M:%S)"`,
242
+ "git submodule sync --recursive || true",
243
+ "if ! git submodule update --init --recursive; then",
244
+ ` printf '\\n[%s] Submodule update failed; discarding submodule state and retrying from scratch\\n' "$(date -u +%H:%M:%S)"`,
245
+ " git submodule deinit -f --all || true",
246
+ " rm -rf .git/modules",
247
+ " git submodule update --init --recursive",
248
+ "fi",
249
+ "commit_sha=$(git rev-parse HEAD)",
250
+ `source_tree_listing=$(git ls-tree HEAD -- ${SDK_PREPARE_SOURCE_PATHS.map(shellQuote).join(" ")})`,
251
+ `source_fingerprint=$(SOURCE_TREE_LISTING="$source_tree_listing" node <<'NODE'`,
252
+ "const crypto = require('node:crypto');",
253
+ "const input = `sdk-prepare-source-v1\\n${process.env.SOURCE_TREE_LISTING || ''}`;",
254
+ "process.stdout.write(crypto.createHash('sha256').update(input).digest('hex'));",
255
+ "NODE",
256
+ ")",
257
+ 'mkdir -p "$UV_CACHE_DIR" "$CCACHE_DIR" "$(dirname "$cache_state_file")"',
258
+ `printf '\\ncommitSha: %s\\nsourceFingerprint: %s\\npreparedRoot: %s\\ncacheState: %s\\n' "$commit_sha" "$source_fingerprint" "$PWD" "$cache_state_file"`,
259
+ `printf '\\n[%s] Runtime storage before prepare\\n' "$(date -u +%H:%M:%S)"`,
260
+ 'df -h "$runtime_root" || true',
261
+ 'du -sh "$runtime_root"/.agora/cache "$runtime_root"/.agora/cache/uv "$runtime_root"/.agora/cache/ccache 2>/dev/null || true',
262
+ `printf '\\n[%s] Runtime volume usage breakdown\\n' "$(date -u +%H:%M:%S)"`,
263
+ 'du -h -d 1 "$(dirname "$runtime_root")" 2>/dev/null | sort -h | tail -n 40 || true',
264
+ `printf '\\n[%s] SDK runtime root usage breakdown\\n' "$(date -u +%H:%M:%S)"`,
265
+ 'du -h -d 1 "$runtime_root" 2>/dev/null | sort -h | tail -n 60 || true',
266
+ `find "$UV_CACHE_DIR" -mindepth 1 -maxdepth 1 -type d -name '.tmp*' -exec rm -rf {} + 2>/dev/null || true`,
267
+ `printf '\\n[%s] ccache stats before prepare\\n' "$(date -u +%H:%M:%S)"`,
268
+ "ccache --show-stats --verbose || true",
269
+ 'progress cache-check "Checking prepared SDK runtime cache"',
270
+ `printf '\\n[%s] Checking prepared SDK runtime cache\\n' "$(date -u +%H:%M:%S)"`,
271
+ 'if [ -f "$cache_state_file" ]; then',
272
+ ` if AGORA_CACHE_STATE_FILE="$cache_state_file" AGORA_CACHE_REPO_URL=${shellQuote(input.settings.repoUrl)} AGORA_CACHE_BRANCH=${shellQuote(input.settings.branch)} AGORA_CACHE_COMMIT="$commit_sha" AGORA_CACHE_SOURCE_FINGERPRINT="$source_fingerprint" AGORA_CACHE_FINGERPRINT="$cache_fingerprint" AGORA_CACHE_PREPARED_ROOT=${shellQuote(input.settings.preparedRoot)} node <<'NODE'`,
273
+ "const fs = require('node:fs');",
274
+ "const path = require('node:path');",
275
+ "const state = JSON.parse(fs.readFileSync(process.env.AGORA_CACHE_STATE_FILE, 'utf8'));",
276
+ "const ok = state.status === 'ready'",
277
+ " && state.repoUrl === process.env.AGORA_CACHE_REPO_URL",
278
+ " && state.branch === process.env.AGORA_CACHE_BRANCH",
279
+ " && state.fingerprint === process.env.AGORA_CACHE_FINGERPRINT",
280
+ " && (state.sourceFingerprint ? state.sourceFingerprint === process.env.AGORA_CACHE_SOURCE_FINGERPRINT : state.commitSha === process.env.AGORA_CACHE_COMMIT)",
281
+ " && path.resolve(state.preparedRoot) === path.resolve(process.env.AGORA_CACHE_PREPARED_ROOT);",
282
+ "process.exit(ok ? 0 : 1);",
283
+ "NODE",
284
+ " then",
285
+ " if run_smoke_probe; then",
286
+ ` AGORA_CACHE_STATE_FILE="$cache_state_file" AGORA_CACHE_REPO_URL=${shellQuote(input.settings.repoUrl)} AGORA_CACHE_BRANCH=${shellQuote(input.settings.branch)} AGORA_CACHE_COMMIT="$commit_sha" AGORA_CACHE_SOURCE_FINGERPRINT="$source_fingerprint" AGORA_CACHE_FINGERPRINT="$cache_fingerprint" AGORA_CACHE_PREPARED_ROOT=${shellQuote(input.settings.preparedRoot)} node <<'NODE'`,
287
+ "const fs = require('node:fs');",
288
+ "const path = require('node:path');",
289
+ "const statePath = process.env.AGORA_CACHE_STATE_FILE;",
290
+ "const state = {",
291
+ " status: 'ready',",
292
+ " repoUrl: process.env.AGORA_CACHE_REPO_URL,",
293
+ " branch: process.env.AGORA_CACHE_BRANCH,",
294
+ " commitSha: process.env.AGORA_CACHE_COMMIT,",
295
+ " fingerprint: process.env.AGORA_CACHE_FINGERPRINT,",
296
+ " sourceFingerprint: process.env.AGORA_CACHE_SOURCE_FINGERPRINT,",
297
+ " preparedRoot: path.resolve(process.env.AGORA_CACHE_PREPARED_ROOT),",
298
+ " updatedAt: new Date().toISOString(),",
299
+ "};",
300
+ "fs.writeFileSync(statePath, `${JSON.stringify(state, null, 2)}\\n`);",
301
+ "NODE",
302
+ ` printf '\\n[%s] SDK runtime cache hit; prepare command skipped\\n' "$(date -u +%H:%M:%S)"`,
303
+ ' progress complete "SDK runtime cache hit"',
304
+ " exit 0",
305
+ " fi",
306
+ ` printf '\\n[%s] SDK runtime cache marker found, but smoke probe failed\\n' "$(date -u +%H:%M:%S)"`,
307
+ " fi",
308
+ "fi",
309
+ 'progress install "Installing SDK runtime dependencies"',
310
+ `printf '\\n[%s] Running prepare command\\n%s\\n' "$(date -u +%H:%M:%S)" ${shellQuote(input.settings.prepareCommand)}`,
311
+ "set +e",
312
+ `sh -lc ${shellQuote(input.settings.prepareCommand)}`,
313
+ "prepare_code=$?",
314
+ "set -e",
315
+ `if [ "$prepare_code" -ne 0 ]; then printf '\\n[%s] Prepare command failed with exit %s\\n' "$(date -u +%H:%M:%S)" "$prepare_code"; exit "$prepare_code"; fi`,
316
+ `printf '\\n[%s] ccache stats after prepare\\n' "$(date -u +%H:%M:%S)"`,
317
+ "ccache --show-stats --verbose || true",
318
+ 'progress commit "Recording SDK commit"',
319
+ `printf '\\n[%s] Recording SDK commit\\n' "$(date -u +%H:%M:%S)"`,
320
+ `printf '%s\\n' "$commit_sha"`,
321
+ 'progress probe "Verifying SDK runtime lowering smoke"',
322
+ `printf '\\n[%s] Verifying SDK runtime lowering smoke\\n' "$(date -u +%H:%M:%S)"`,
323
+ "run_smoke_probe",
324
+ 'progress cache-write "Recording SDK runtime cache state"',
325
+ `printf '\\n[%s] Recording SDK runtime cache state\\n' "$(date -u +%H:%M:%S)"`,
326
+ `AGORA_CACHE_STATE_FILE="$cache_state_file" AGORA_CACHE_REPO_URL=${shellQuote(input.settings.repoUrl)} AGORA_CACHE_BRANCH=${shellQuote(input.settings.branch)} AGORA_CACHE_COMMIT="$commit_sha" AGORA_CACHE_SOURCE_FINGERPRINT="$source_fingerprint" AGORA_CACHE_FINGERPRINT="$cache_fingerprint" AGORA_CACHE_PREPARED_ROOT=${shellQuote(input.settings.preparedRoot)} node <<'NODE'`,
327
+ "const fs = require('node:fs');",
328
+ "const path = require('node:path');",
329
+ "const statePath = process.env.AGORA_CACHE_STATE_FILE;",
330
+ "const state = {",
331
+ " status: 'ready',",
332
+ " repoUrl: process.env.AGORA_CACHE_REPO_URL,",
333
+ " branch: process.env.AGORA_CACHE_BRANCH,",
334
+ " commitSha: process.env.AGORA_CACHE_COMMIT,",
335
+ " fingerprint: process.env.AGORA_CACHE_FINGERPRINT,",
336
+ " sourceFingerprint: process.env.AGORA_CACHE_SOURCE_FINGERPRINT,",
337
+ " preparedRoot: path.resolve(process.env.AGORA_CACHE_PREPARED_ROOT),",
338
+ " updatedAt: new Date().toISOString(),",
339
+ "};",
340
+ "fs.writeFileSync(statePath, `${JSON.stringify(state, null, 2)}\\n`);",
341
+ "NODE",
342
+ 'progress complete "SDK prepare command completed"'
343
+ ].join("\n");
344
+ }
345
+ function shellQuote(value) {
346
+ return `'${value.replace(/'/g, "'\\''")}'`;
347
+ }
348
+
349
+ // lib/server/sdk-runtime-kubernetes-job-spec.ts
350
+ function sdkPrepareJobSpec(input) {
351
+ return {
352
+ apiVersion: "batch/v1",
353
+ kind: "Job",
354
+ metadata: {
355
+ name: input.jobName,
356
+ namespace: input.namespace,
357
+ labels: { app: "legato-dev-agora", "agora.hyperaccel.net/job": "sdk-prepare" }
358
+ },
359
+ spec: {
360
+ ttlSecondsAfterFinished: 3600,
361
+ activeDeadlineSeconds: 14400,
362
+ backoffLimit: 0,
363
+ template: {
364
+ metadata: {
365
+ labels: { app: "legato-dev-agora", "agora.hyperaccel.net/job": "sdk-prepare" },
366
+ annotations: { "sidecar.istio.io/inject": "false" }
367
+ },
368
+ spec: {
369
+ restartPolicy: "Never",
370
+ serviceAccountName: "legato-dev-agora",
371
+ imagePullSecrets: [{ name: "harbor-secret" }],
372
+ affinity: {
373
+ podAffinity: {
374
+ requiredDuringSchedulingIgnoredDuringExecution: [{
375
+ labelSelector: { matchLabels: { app: "legato-dev-agora" } },
376
+ topologyKey: "kubernetes.io/hostname"
377
+ }]
378
+ }
379
+ },
380
+ containers: [{
381
+ name: "sdk-prepare",
382
+ image: input.image,
383
+ imagePullPolicy: "Always",
384
+ command: ["sh", "-lc", sdkPrepareShellScript(input)],
385
+ env: [
386
+ {
387
+ name: "AGORA_GITHUB_TOKEN",
388
+ valueFrom: { secretKeyRef: { name: input.secretName, key: "token", optional: true } }
389
+ },
390
+ // Forwarded from the web pod: the prepare command installs the SDK
391
+ // team's published wheels when an index is configured, and builds
392
+ // from source when it is not.
393
+ ...sdkPackageIndexEnv(process.env, input.pin)
394
+ ],
395
+ volumeMounts: [{ name: "data", mountPath: "/data" }],
396
+ resources: {
397
+ requests: { cpu: "500m", memory: "1Gi" },
398
+ limits: { cpu: "2000m", memory: "4Gi" }
399
+ }
400
+ }],
401
+ volumes: [{ name: "data", persistentVolumeClaim: { claimName: input.pvcName } }]
402
+ }
403
+ }
404
+ }
405
+ };
406
+ }
407
+
408
+ // lib/server/sdk-runtime-kubernetes-job.ts
409
+ var JOB_TIMEOUT_MS = 144e5;
410
+ var POLL_INTERVAL_MS = 2e3;
411
+ async function runSdkPrepareJob(settings, run, statusRoot = settings.preparedRoot, pin) {
412
+ const client = await inClusterKubernetesClient();
413
+ const jobName = sdkPrepareJobName(settings);
414
+ const spec = sdkPrepareJobSpec({
415
+ settings,
416
+ statusRoot,
417
+ namespace: client.namespace,
418
+ jobName,
419
+ image: process.env.AGORA_SDK_PREPARE_JOB_IMAGE ?? "cr.hyperaccel.net/hyperaccel/legato-dev-agora:latest",
420
+ secretName: process.env.AGORA_GITHUB_SECRET_NAME ?? "legato-dev-agora-github",
421
+ pvcName: process.env.AGORA_DATA_PVC_NAME ?? "legato-dev-agora-data",
422
+ runId: run.runId,
423
+ progressPath: run.progressPath,
424
+ liveLogPath: run.liveLogPath,
425
+ resultPath: run.resultPath,
426
+ pin
427
+ });
428
+ const created = await client.request({ method: "POST", path: `/apis/batch/v1/namespaces/${client.namespace}/jobs`, body: spec });
429
+ if (created.statusCode >= 400) {
430
+ return { succeeded: false, logs: created.body, message: `Kubernetes SDK prepare job creation failed: HTTP ${created.statusCode}` };
431
+ }
432
+ return await waitForJob(client, jobName, readPositiveInt(process.env.AGORA_SDK_PREPARE_JOB_TIMEOUT_MS, JOB_TIMEOUT_MS));
433
+ }
434
+ async function waitForJob(client, jobName, timeoutMs) {
435
+ const started = Date.now();
436
+ while (Date.now() - started < timeoutMs) {
437
+ const job = await client.request({ method: "GET", path: `/apis/batch/v1/namespaces/${client.namespace}/jobs/${jobName}` });
438
+ if (job.statusCode >= 400) return { succeeded: false, logs: "", message: `Kubernetes job lookup failed: HTTP ${job.statusCode}` };
439
+ const status = readRecord(readRecord(JSON.parse(job.body), "status"), void 0);
440
+ if (readNumber(status, "succeeded") > 0) return { succeeded: true, logs: await podLogs(client, jobName, "sdk-prepare") };
441
+ if (readNumber(status, "failed") > 0) {
442
+ const details = await podFailureReason(client, jobName);
443
+ return {
444
+ succeeded: false,
445
+ logs: joinLogSections(await podLogs(client, jobName, "sdk-prepare"), details ? `Kubernetes failure details:
446
+ ${details}` : ""),
447
+ message: details ? `SDK prepare job failed: ${firstLine(details)}` : "SDK prepare job failed."
448
+ };
449
+ }
450
+ await delay(POLL_INTERVAL_MS);
451
+ }
452
+ return { succeeded: false, logs: await podLogs(client, jobName, "sdk-prepare"), message: "SDK prepare job timed out." };
453
+ }
454
+ function sdkPrepareJobName(settings) {
455
+ const suffix = randomUUID().replace(/-/g, "").slice(0, 10);
456
+ const branch = settings.branch.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 24) || "branch";
457
+ return `sdk-prepare-${branch}-${suffix}`.slice(0, 63);
458
+ }
459
+ function joinLogSections(...sections) {
460
+ return sections.map((section) => section.trim()).filter(Boolean).join("\n\n");
461
+ }
462
+ function firstLine(text) {
463
+ return text.split("\n").map((line) => line.trim()).find(Boolean) ?? "unknown Kubernetes failure";
464
+ }
465
+ function readPositiveInt(value, fallback) {
466
+ if (!value) return fallback;
467
+ const parsed = Number.parseInt(value, 10);
468
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
469
+ }
470
+ async function delay(ms) {
471
+ await new Promise((resolve) => {
472
+ setTimeout(resolve, ms);
473
+ });
474
+ }
475
+ export {
476
+ kubernetesApiEndpointFromEnv,
477
+ runSdkPrepareJob,
478
+ sdkPrepareJobSpec
479
+ };
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import os
6
+ import subprocess
7
+ import sys
8
+ from pathlib import Path
9
+
10
+
11
+ SMOKE_KERNEL = '''#!/usr/bin/env python3
12
+ from __future__ import annotations
13
+
14
+ from pathlib import Path
15
+ import os
16
+ import sys
17
+
18
+
19
+ def _prepare_imports(repo_root: Path) -> None:
20
+ for path in (repo_root, repo_root / "legato" / "src"):
21
+ text = str(path)
22
+ if text not in sys.path:
23
+ sys.path.insert(0, text)
24
+
25
+
26
+ def _prepare_bertha(ctx):
27
+ import legato.model.bertha as bertha_model
28
+
29
+ return bertha_model.Bertha(ctx, "default", 32, False, 8, 128 * pow(1024, 3))
30
+
31
+
32
+ def run(output_type_name: str, output_root: Path) -> None:
33
+ import torch
34
+
35
+ import legato
36
+ import legato.model.bertha as bertha
37
+ from legato.language import arith
38
+
39
+ @legato.compile(
40
+ backend=_prepare_bertha,
41
+ param_kinds={"tensor_a": "universal", "tensor_b": "top", "tensor_out": "core"},
42
+ )
43
+ def agora_runtime_smoke_kernel(
44
+ tensor_a: legato.get_model().tensor_type(
45
+ legato.types.float("bfloat16"), (32, (-1, 8), (-1, 32)), "shared_dram"
46
+ ),
47
+ tensor_b: legato.get_model().tensor_type(
48
+ legato.types.float("bfloat16"), (32, (-1, 32), 128), "shared_dram"
49
+ ),
50
+ tensor_out: legato.get_model().tensor_type(
51
+ legato.types.float("bfloat16"), ((-1, 256), 128), "shared_dram"
52
+ ),
53
+ ):
54
+ device = legato.get_context().get_device()
55
+ seq_len = legato.tensor.get_dimension(tensor_a, 1)
56
+ sram_a_type = legato.get_model().tensor_type(
57
+ legato.types.float("bfloat16"), ((-1, 8), (-1, 64)), "row_major"
58
+ )
59
+ sram_b_type = legato.get_model().tensor_type(
60
+ legato.types.float("bfloat16"), ((-1, 32), 128), "row_major"
61
+ )
62
+ zero = arith.cast(0, legato.types.index())
63
+ one = arith.cast(1, legato.types.index())
64
+ batch_end = arith.cast(32, legato.types.index())
65
+
66
+ with device.get_top():
67
+ for i in legato.range(zero, batch_end):
68
+ a_i_dram = legato.tensor.view(
69
+ tensor_a,
70
+ (legato.range(i, i + one), legato.range(0, seq_len), legato.range(0, seq_len)),
71
+ )
72
+ b_i_dram = legato.tensor.view(
73
+ tensor_b,
74
+ (legato.range(i, i + one), legato.range(0, seq_len), legato.range(0, 128)),
75
+ )
76
+ legato.tensor.request_load(a_i_dram, device.get_core(0), "sram")
77
+ legato.tensor.request_load(b_i_dram, device.get_core(0), "sram")
78
+
79
+ with device.get_core(0):
80
+ for i in legato.range(zero, batch_end):
81
+ loaded_a_i = legato.tensor.receive_type(sram_a_type, 0, (seq_len, seq_len), "sram")
82
+ loaded_b_i = legato.tensor.receive_type(sram_b_type, 1, (seq_len,), "sram")
83
+ z_i = loaded_a_i @ loaded_b_i
84
+ row_start_i = seq_len * i
85
+ row_end_i = seq_len * (i + one)
86
+ out_i = legato.tensor.view(
87
+ tensor_out,
88
+ (legato.range(row_start_i, row_end_i), legato.range(0, 128)),
89
+ )
90
+ legato.tensor.memcpy(out_i, z_i)
91
+
92
+ seq_len = 8
93
+ tensor_a = torch.randn(32, seq_len, seq_len, dtype=torch.bfloat16)
94
+ tensor_b = torch.randn(32, seq_len, 128, dtype=torch.bfloat16)
95
+ tensor_out = torch.zeros(32 * seq_len, 128, dtype=torch.bfloat16)
96
+ output_type = getattr(legato.OutputType, output_type_name)
97
+ with legato.session(output_type=output_type, output_path=str(output_root)):
98
+ agora_runtime_smoke_kernel(tensor_a, legato.Arg(tensor_b, bertha.top), legato.Arg(tensor_out, bertha.core(0)))
99
+
100
+
101
+ def main() -> int:
102
+ repo_root = Path(os.environ.get("AGORA_SDK_SMOKE_REPO_ROOT", Path.cwd())).resolve()
103
+ output_type = os.environ["AGORA_SDK_SMOKE_OUTPUT_TYPE"]
104
+ output_root = Path(os.environ["AGORA_SDK_SMOKE_OUTPUT_ROOT"]).resolve()
105
+ os.chdir(repo_root)
106
+ _prepare_imports(repo_root)
107
+ run(output_type, output_root)
108
+ return 0
109
+
110
+
111
+ if __name__ == "__main__":
112
+ raise SystemExit(main())
113
+ '''
114
+
115
+
116
+ def parse_args() -> argparse.Namespace:
117
+ parser = argparse.ArgumentParser(description="Run prepared SDK lowering smoke")
118
+ parser.add_argument("--repo-root", required=True)
119
+ parser.add_argument("--output-root", required=True)
120
+ parser.add_argument("--stages", default="MLIR")
121
+ parser.add_argument("--runner", default=str(Path(__file__).resolve().with_name("legato-lowering-runner.py")))
122
+ return parser.parse_args()
123
+
124
+
125
+ def main() -> int:
126
+ args = parse_args()
127
+ repo_root = Path(args.repo_root).resolve()
128
+ output_root = Path(args.output_root).resolve()
129
+ smoke_root = repo_root / ".agora" / "smoke"
130
+ kernel_path = smoke_root / "agora_runtime_smoke.py"
131
+ kernel_path.parent.mkdir(parents=True, exist_ok=True)
132
+ output_root.mkdir(parents=True, exist_ok=True)
133
+ kernel_path.write_text(SMOKE_KERNEL, encoding="utf-8")
134
+
135
+ stages = [stage.strip() for stage in args.stages.split(",") if stage.strip()]
136
+ if not stages:
137
+ stages = ["MLIR"]
138
+
139
+ print(f"[SDK_SMOKE] repo_root={repo_root}", flush=True)
140
+ print(f"[SDK_SMOKE] stages={','.join(stages)}", flush=True)
141
+ for stage in stages:
142
+ stage_output = output_root / stage.lower()
143
+ stage_output.mkdir(parents=True, exist_ok=True)
144
+ env = {
145
+ **dict(os.environ),
146
+ "AGORA_SDK_SMOKE_REPO_ROOT": str(repo_root),
147
+ "AGORA_SDK_SMOKE_OUTPUT_TYPE": stage,
148
+ "AGORA_SDK_SMOKE_OUTPUT_ROOT": str(stage_output),
149
+ }
150
+ command = [
151
+ sys.executable,
152
+ str(Path(args.runner).resolve()),
153
+ "--kernel",
154
+ str(kernel_path),
155
+ "--output-root",
156
+ str(output_root),
157
+ "--stages",
158
+ stage,
159
+ ]
160
+ print(f"[SDK_SMOKE] stage={stage} status=running", flush=True)
161
+ subprocess.run(command, cwd=repo_root, env=env, check=True)
162
+ print(f"[SDK_SMOKE] stage={stage} status=success", flush=True)
163
+ print("[SDK_SMOKE] lowering_smoke=success", flush=True)
164
+ return 0
165
+
166
+
167
+ if __name__ == "__main__":
168
+ raise SystemExit(main())