@agent-native/core 0.98.1 → 0.98.2
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/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +8 -0
- package/corpus/core/docs/content/locales/ar-SA/pr-visual-recap.mdx +25 -1
- package/corpus/core/docs/content/locales/ar-SA/template-plan.mdx +1 -1
- package/corpus/core/docs/content/locales/de-DE/pr-visual-recap.mdx +25 -1
- package/corpus/core/docs/content/locales/de-DE/template-plan.mdx +1 -1
- package/corpus/core/docs/content/locales/es-ES/pr-visual-recap.mdx +25 -1
- package/corpus/core/docs/content/locales/es-ES/template-plan.mdx +1 -1
- package/corpus/core/docs/content/locales/fr-FR/pr-visual-recap.mdx +25 -1
- package/corpus/core/docs/content/locales/fr-FR/template-plan.mdx +1 -1
- package/corpus/core/docs/content/locales/hi-IN/pr-visual-recap.mdx +25 -1
- package/corpus/core/docs/content/locales/hi-IN/template-plan.mdx +1 -1
- package/corpus/core/docs/content/locales/ja-JP/pr-visual-recap.mdx +25 -1
- package/corpus/core/docs/content/locales/ja-JP/template-plan.mdx +1 -1
- package/corpus/core/docs/content/locales/ko-KR/pr-visual-recap.mdx +25 -1
- package/corpus/core/docs/content/locales/ko-KR/template-plan.mdx +1 -1
- package/corpus/core/docs/content/locales/pt-BR/pr-visual-recap.mdx +25 -1
- package/corpus/core/docs/content/locales/pt-BR/template-plan.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-CN/pr-visual-recap.mdx +25 -1
- package/corpus/core/docs/content/locales/zh-CN/template-plan.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-TW/pr-visual-recap.mdx +25 -1
- package/corpus/core/docs/content/locales/zh-TW/template-plan.mdx +1 -1
- package/corpus/core/docs/content/pr-visual-recap.mdx +50 -1
- package/corpus/core/docs/content/template-plan.mdx +1 -1
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/plan-local.ts +43 -11
- package/corpus/core/src/cli/pr-visual-recap-workflow.ts +2 -9
- package/corpus/core/src/cli/recap.ts +356 -4
- package/corpus/core/src/cli/skills-content/local-files.ts +17 -12
- package/corpus/core/src/client/analytics.ts +96 -14
- package/corpus/core/src/client/index.ts +5 -0
- package/corpus/core/src/client/session-replay.ts +10 -0
- package/corpus/core/src/client/settings/DemoModeSection.tsx +4 -28
- package/corpus/core/src/client/url-scrub.ts +13 -0
- package/corpus/core/src/client/use-demo-mode-status.ts +44 -0
- package/corpus/templates/analytics/AGENTS.md +8 -0
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +18 -4
- package/corpus/templates/analytics/app/lib/demo-chart-trend.ts +156 -0
- package/corpus/templates/analytics/changelog/2026-07-12-daily-dashboard-emails-reliably-include-the-complete-dashboa.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-demo-mode-charts-trend-upward.md +6 -0
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +2 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +17 -33
- package/corpus/templates/clips/app/components/player/video-player.tsx +5 -1
- package/corpus/templates/clips/changelog/2026-07-11-clips-no-longer-stay-stuck-on-preparing-clip-when-they-are-r.md +6 -0
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +2 -2
- package/corpus/templates/content/changelog/2026-07-12-the-left-sidebar-stays-fixed-horizontally-while-scrolling-on.md +6 -0
- package/corpus/templates/plan/.agents/skills/visual-plan/references/local-files.md +17 -12
- package/corpus/templates/plan/.agents/skills/visual-recap/references/local-files.md +17 -12
- package/corpus/templates/plan/actions/validate-local-plan-source.ts +4 -4
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +2 -1
- package/corpus/templates/plan/app/lib/plan-local-bridge.ts +41 -0
- package/corpus/templates/plan/app/lib/plan-tracking.ts +3 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +9 -9
- package/corpus/templates/plan/app/root.tsx +13 -3
- package/corpus/templates/plan/changelog/2026-07-12-local-plan-source-now-stays-on-device.md +6 -0
- package/dist/cli/plan-local.d.ts +6 -6
- package/dist/cli/plan-local.d.ts.map +1 -1
- package/dist/cli/plan-local.js +36 -7
- package/dist/cli/plan-local.js.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts +2 -8
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.js +2 -8
- package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
- package/dist/cli/recap.d.ts +22 -1
- package/dist/cli/recap.d.ts.map +1 -1
- package/dist/cli/recap.js +286 -2
- package/dist/cli/recap.js.map +1 -1
- package/dist/cli/skills-content/local-files.d.ts +1 -1
- package/dist/cli/skills-content/local-files.d.ts.map +1 -1
- package/dist/cli/skills-content/local-files.js +17 -12
- package/dist/cli/skills-content/local-files.js.map +1 -1
- package/dist/client/analytics.d.ts +8 -0
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +77 -13
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/index.d.ts +2 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/session-replay.d.ts +2 -0
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +7 -0
- package/dist/client/session-replay.js.map +1 -1
- package/dist/client/settings/DemoModeSection.d.ts.map +1 -1
- package/dist/client/settings/DemoModeSection.js +4 -23
- package/dist/client/settings/DemoModeSection.js.map +1 -1
- package/dist/client/url-scrub.d.ts.map +1 -1
- package/dist/client/url-scrub.js +14 -0
- package/dist/client/url-scrub.js.map +1 -1
- package/dist/client/use-demo-mode-status.d.ts +12 -0
- package/dist/client/use-demo-mode-status.d.ts.map +1 -0
- package/dist/client/use-demo-mode-status.js +30 -0
- package/dist/client/use-demo-mode-status.js.map +1 -0
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/secrets/routes.d.ts +9 -9
- package/docs/content/locales/ar-SA/pr-visual-recap.mdx +25 -1
- package/docs/content/locales/ar-SA/template-plan.mdx +1 -1
- package/docs/content/locales/de-DE/pr-visual-recap.mdx +25 -1
- package/docs/content/locales/de-DE/template-plan.mdx +1 -1
- package/docs/content/locales/es-ES/pr-visual-recap.mdx +25 -1
- package/docs/content/locales/es-ES/template-plan.mdx +1 -1
- package/docs/content/locales/fr-FR/pr-visual-recap.mdx +25 -1
- package/docs/content/locales/fr-FR/template-plan.mdx +1 -1
- package/docs/content/locales/hi-IN/pr-visual-recap.mdx +25 -1
- package/docs/content/locales/hi-IN/template-plan.mdx +1 -1
- package/docs/content/locales/ja-JP/pr-visual-recap.mdx +25 -1
- package/docs/content/locales/ja-JP/template-plan.mdx +1 -1
- package/docs/content/locales/ko-KR/pr-visual-recap.mdx +25 -1
- package/docs/content/locales/ko-KR/template-plan.mdx +1 -1
- package/docs/content/locales/pt-BR/pr-visual-recap.mdx +25 -1
- package/docs/content/locales/pt-BR/template-plan.mdx +1 -1
- package/docs/content/locales/zh-CN/pr-visual-recap.mdx +25 -1
- package/docs/content/locales/zh-CN/template-plan.mdx +1 -1
- package/docs/content/locales/zh-TW/pr-visual-recap.mdx +25 -1
- package/docs/content/locales/zh-TW/template-plan.mdx +1 -1
- package/docs/content/pr-visual-recap.mdx +50 -1
- package/docs/content/template-plan.mdx +1 -1
- package/package.json +1 -1
package/dist/cli/recap.js
CHANGED
|
@@ -86,6 +86,8 @@ export const PR_VISUAL_RECAP_SETUP = [
|
|
|
86
86
|
" VISUAL_RECAP_API_KEY (secret) + VISUAL_RECAP_AGENT=openai-compatible + VISUAL_RECAP_BASE_URL (variable) — use DeepSeek, Kimi, or any OpenAI-compatible API",
|
|
87
87
|
" VISUAL_RECAP_MODEL (variable, required for openai-compatible) — provider model id; optional override for Claude/Codex",
|
|
88
88
|
" VISUAL_RECAP_REASONING (variable) — reasoning depth (none|minimal|low|medium|high|xhigh; Codex only)",
|
|
89
|
+
' VISUAL_RECAP_RUNS_ON (variable) — JSON hosted label or self-hosted label array; defaults to "ubuntu-latest"',
|
|
90
|
+
" VISUAL_RECAP_GATE_RUNS_ON (variable) — trusted same-repo authors only; plain single gate label; defaults to ubuntu-latest",
|
|
89
91
|
" VISUAL_RECAP_SKILL_SOURCE=repo (variable) — pin CI to the repo-local visual-recap skill instead of latest bundled guidance",
|
|
90
92
|
" VISUAL_RECAP_SECRET_SCAN=off|high-confidence|strict (variable) — default high-confidence; strict restores generic TOKEN/SECRET assignment suppression",
|
|
91
93
|
" PLAN_RECAP_APP_URL (secret) — only when self-hosting the plan app (defaults to https://plan.agent-native.com)",
|
|
@@ -133,6 +135,12 @@ export function buildReusableCallerWorkflow(options = {}) {
|
|
|
133
135
|
const ref = (options.ref ?? "main").replace(/^@/, "");
|
|
134
136
|
const agentValue = options.agent ?? "${{ vars.VISUAL_RECAP_AGENT || 'claude' }}";
|
|
135
137
|
const modelValue = options.model ?? "${{ vars.VISUAL_RECAP_MODEL || '' }}";
|
|
138
|
+
const runsOnValue = options.runsOn === undefined
|
|
139
|
+
? "${{ vars.VISUAL_RECAP_RUNS_ON || '\"ubuntu-latest\"' }}"
|
|
140
|
+
: JSON.stringify(options.runsOn);
|
|
141
|
+
const gateRunsOnValue = options.gateRunsOn === undefined
|
|
142
|
+
? "${{ vars.VISUAL_RECAP_GATE_RUNS_ON || 'ubuntu-latest' }}"
|
|
143
|
+
: JSON.stringify(options.gateRunsOn);
|
|
136
144
|
return (`name: PR Visual Recap\n` +
|
|
137
145
|
`\n` +
|
|
138
146
|
`# Thin caller — the full workflow logic lives in BuilderIO/agent-native.\n` +
|
|
@@ -166,6 +174,8 @@ export function buildReusableCallerWorkflow(options = {}) {
|
|
|
166
174
|
` reasoning: \${{ vars.VISUAL_RECAP_REASONING || '' }}\n` +
|
|
167
175
|
` skill-source: \${{ vars.VISUAL_RECAP_SKILL_SOURCE || 'auto' }}\n` +
|
|
168
176
|
` secret-scan: \${{ vars.VISUAL_RECAP_SECRET_SCAN || 'high-confidence' }}\n` +
|
|
177
|
+
` runs-on: ${runsOnValue}\n` +
|
|
178
|
+
` gate-runs-on: ${gateRunsOnValue}\n` +
|
|
169
179
|
` # cli-version: "latest" # pin to a specific @agent-native/core version\n` +
|
|
170
180
|
``);
|
|
171
181
|
}
|
|
@@ -181,6 +191,8 @@ export function writePrVisualRecapReusableCallerWorkflow(baseDir, options = {})
|
|
|
181
191
|
ref: options.ref,
|
|
182
192
|
agent: options.agent,
|
|
183
193
|
model: options.model,
|
|
194
|
+
runsOn: options.runsOn,
|
|
195
|
+
gateRunsOn: options.gateRunsOn,
|
|
184
196
|
});
|
|
185
197
|
if (fs.existsSync(file)) {
|
|
186
198
|
const current = fs.readFileSync(file, "utf8");
|
|
@@ -330,6 +342,40 @@ function listGithubVariables(repo) {
|
|
|
330
342
|
return null;
|
|
331
343
|
}
|
|
332
344
|
}
|
|
345
|
+
function listGithubOrganizationVariables(repo) {
|
|
346
|
+
const result = gh([
|
|
347
|
+
"api",
|
|
348
|
+
"--paginate",
|
|
349
|
+
"--slurp",
|
|
350
|
+
`repos/${repo}/actions/organization-variables?per_page=30`,
|
|
351
|
+
]);
|
|
352
|
+
if (!result.ok)
|
|
353
|
+
return null;
|
|
354
|
+
try {
|
|
355
|
+
const parsed = JSON.parse(result.stdout);
|
|
356
|
+
const pages = Array.isArray(parsed) ? parsed : [parsed];
|
|
357
|
+
const out = new Map();
|
|
358
|
+
for (const page of pages) {
|
|
359
|
+
if (!page || typeof page !== "object")
|
|
360
|
+
continue;
|
|
361
|
+
const variables = page.variables;
|
|
362
|
+
if (!Array.isArray(variables))
|
|
363
|
+
continue;
|
|
364
|
+
for (const variable of variables) {
|
|
365
|
+
if (!variable || typeof variable !== "object")
|
|
366
|
+
continue;
|
|
367
|
+
const record = variable;
|
|
368
|
+
if (typeof record.name !== "string")
|
|
369
|
+
continue;
|
|
370
|
+
out.set(record.name, typeof record.value === "string" ? record.value : "");
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return out;
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
333
379
|
function setGithubSecret(name, value, repo, dryRun) {
|
|
334
380
|
if (!value)
|
|
335
381
|
return "missing";
|
|
@@ -350,6 +396,61 @@ function setGithubVariable(name, value, repo, dryRun) {
|
|
|
350
396
|
args.push("--repo", repo);
|
|
351
397
|
return gh(args).ok ? "set" : "failed";
|
|
352
398
|
}
|
|
399
|
+
function listGithubRunners(repo) {
|
|
400
|
+
const result = gh([
|
|
401
|
+
"api",
|
|
402
|
+
"--paginate",
|
|
403
|
+
"--slurp",
|
|
404
|
+
`repos/${repo}/actions/runners?per_page=100`,
|
|
405
|
+
]);
|
|
406
|
+
if (!result.ok)
|
|
407
|
+
return null;
|
|
408
|
+
try {
|
|
409
|
+
const parsed = JSON.parse(result.stdout);
|
|
410
|
+
const pages = Array.isArray(parsed) ? parsed : [parsed];
|
|
411
|
+
const runners = [];
|
|
412
|
+
for (const page of pages) {
|
|
413
|
+
if (!page || typeof page !== "object")
|
|
414
|
+
continue;
|
|
415
|
+
const entries = page.runners;
|
|
416
|
+
if (!Array.isArray(entries))
|
|
417
|
+
continue;
|
|
418
|
+
for (const entry of entries) {
|
|
419
|
+
if (!entry || typeof entry !== "object")
|
|
420
|
+
continue;
|
|
421
|
+
const record = entry;
|
|
422
|
+
if (typeof record.name !== "string")
|
|
423
|
+
continue;
|
|
424
|
+
const rawLabels = Array.isArray(record.labels) ? record.labels : [];
|
|
425
|
+
runners.push({
|
|
426
|
+
name: record.name,
|
|
427
|
+
status: typeof record.status === "string" ? record.status : "",
|
|
428
|
+
labels: rawLabels.flatMap((label) => {
|
|
429
|
+
if (typeof label === "string")
|
|
430
|
+
return [label];
|
|
431
|
+
if (!label || typeof label !== "object")
|
|
432
|
+
return [];
|
|
433
|
+
const name = label.name;
|
|
434
|
+
return typeof name === "string" ? [name] : [];
|
|
435
|
+
}),
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return runners;
|
|
440
|
+
}
|
|
441
|
+
catch {
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
export function matchingRecapRunners(runners, requiredLabels) {
|
|
446
|
+
const required = requiredLabels.map((label) => label.toLowerCase());
|
|
447
|
+
return runners.filter((runner) => {
|
|
448
|
+
if (runner.status.toLowerCase() !== "online")
|
|
449
|
+
return false;
|
|
450
|
+
const labels = new Set(runner.labels.map((label) => label.toLowerCase()));
|
|
451
|
+
return required.every((label) => labels.has(label));
|
|
452
|
+
});
|
|
453
|
+
}
|
|
353
454
|
const OPENAI_COMPATIBLE_VARIABLE_REQUIREMENTS = [
|
|
354
455
|
{
|
|
355
456
|
name: "VISUAL_RECAP_BASE_URL",
|
|
@@ -357,6 +458,83 @@ const OPENAI_COMPATIBLE_VARIABLE_REQUIREMENTS = [
|
|
|
357
458
|
},
|
|
358
459
|
{ name: "VISUAL_RECAP_MODEL", example: "provider-model-id" },
|
|
359
460
|
];
|
|
461
|
+
const RECAP_RUNS_ON_REQUIREMENT = {
|
|
462
|
+
name: "VISUAL_RECAP_RUNS_ON",
|
|
463
|
+
example: '["self-hosted","linux","x64","visual-recap"]',
|
|
464
|
+
};
|
|
465
|
+
const RECAP_GATE_RUNS_ON_REQUIREMENT = {
|
|
466
|
+
name: "VISUAL_RECAP_GATE_RUNS_ON",
|
|
467
|
+
example: "visual-recap-gate",
|
|
468
|
+
};
|
|
469
|
+
/** Parse the JSON consumed by GitHub Actions `fromJSON(...)` for `runs-on`. */
|
|
470
|
+
export function parseRecapRunsOn(value) {
|
|
471
|
+
let parsed;
|
|
472
|
+
try {
|
|
473
|
+
parsed = JSON.parse(value);
|
|
474
|
+
}
|
|
475
|
+
catch {
|
|
476
|
+
throw new Error('VISUAL_RECAP_RUNS_ON must be valid JSON, such as "ubuntu-latest" or ["self-hosted","linux","x64"]');
|
|
477
|
+
}
|
|
478
|
+
if (typeof parsed === "string") {
|
|
479
|
+
if (!/^(?:ubuntu|windows|macos)-[A-Za-z0-9.-]+$/.test(parsed)) {
|
|
480
|
+
throw new Error("VISUAL_RECAP_RUNS_ON JSON strings must name a standard GitHub-hosted ubuntu-, windows-, or macos- runner");
|
|
481
|
+
}
|
|
482
|
+
return {
|
|
483
|
+
json: JSON.stringify(parsed),
|
|
484
|
+
labels: [parsed],
|
|
485
|
+
selfHosted: false,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
if (!Array.isArray(parsed) || parsed.length === 0 || parsed.length > 20) {
|
|
489
|
+
throw new Error("VISUAL_RECAP_RUNS_ON must be a hosted runner JSON string or an array of 1-20 self-hosted labels");
|
|
490
|
+
}
|
|
491
|
+
if (parsed.some((label) => typeof label !== "string" ||
|
|
492
|
+
label.length === 0 ||
|
|
493
|
+
label.length > 100 ||
|
|
494
|
+
/[\p{C}]/u.test(label))) {
|
|
495
|
+
throw new Error("VISUAL_RECAP_RUNS_ON labels must be non-empty strings up to 100 characters without control characters");
|
|
496
|
+
}
|
|
497
|
+
const labels = parsed;
|
|
498
|
+
if (!labels.includes("self-hosted")) {
|
|
499
|
+
throw new Error('VISUAL_RECAP_RUNS_ON label arrays must include the exact "self-hosted" label');
|
|
500
|
+
}
|
|
501
|
+
if (new Set(labels.map((label) => label.toLowerCase())).size !== labels.length) {
|
|
502
|
+
throw new Error("VISUAL_RECAP_RUNS_ON labels must be unique");
|
|
503
|
+
}
|
|
504
|
+
return { json: JSON.stringify(labels), labels, selfHosted: true };
|
|
505
|
+
}
|
|
506
|
+
/** Validate the plain label used directly by the gate job's `runs-on`. */
|
|
507
|
+
export function parseRecapGateRunsOn(value) {
|
|
508
|
+
const label = value.trim();
|
|
509
|
+
if (!/^[A-Za-z0-9._-]{1,100}$/.test(label)) {
|
|
510
|
+
throw new Error("VISUAL_RECAP_GATE_RUNS_ON must be one plain runner label (1-100 letters, numbers, dots, underscores, or hyphens)");
|
|
511
|
+
}
|
|
512
|
+
return label;
|
|
513
|
+
}
|
|
514
|
+
function recapRunsOnProblem(value) {
|
|
515
|
+
try {
|
|
516
|
+
parseRecapRunsOn(value);
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
catch (error) {
|
|
520
|
+
return {
|
|
521
|
+
requirement: RECAP_RUNS_ON_REQUIREMENT,
|
|
522
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
function recapGateRunsOnProblem(value) {
|
|
527
|
+
try {
|
|
528
|
+
parseRecapGateRunsOn(value);
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
return {
|
|
533
|
+
requirement: RECAP_GATE_RUNS_ON_REQUIREMENT,
|
|
534
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
}
|
|
360
538
|
const RECAP_MODEL_PATTERN = /^[a-zA-Z0-9._-]{1,80}$/;
|
|
361
539
|
const OPENAI_COMPATIBLE_RECAP_MODEL_PATTERN = /^[^\p{C}\p{Z}]{1,200}$/u;
|
|
362
540
|
export function validateOpenAiCompatibleRecapVariables(input) {
|
|
@@ -427,6 +605,23 @@ export function buildRecapSetupPlan(input) {
|
|
|
427
605
|
model: variableValues.VISUAL_RECAP_MODEL,
|
|
428
606
|
})
|
|
429
607
|
: [];
|
|
608
|
+
const runsOn = input.runsOn ?? envValue(env, "VISUAL_RECAP_RUNS_ON");
|
|
609
|
+
if (runsOn) {
|
|
610
|
+
const problem = recapRunsOnProblem(runsOn);
|
|
611
|
+
if (problem)
|
|
612
|
+
variableProblems.push(problem);
|
|
613
|
+
else
|
|
614
|
+
variableValues.VISUAL_RECAP_RUNS_ON = parseRecapRunsOn(runsOn).json;
|
|
615
|
+
}
|
|
616
|
+
const gateRunsOn = input.gateRunsOn ?? envValue(env, "VISUAL_RECAP_GATE_RUNS_ON");
|
|
617
|
+
if (gateRunsOn) {
|
|
618
|
+
const problem = recapGateRunsOnProblem(gateRunsOn);
|
|
619
|
+
if (problem)
|
|
620
|
+
variableProblems.push(problem);
|
|
621
|
+
else
|
|
622
|
+
variableValues.VISUAL_RECAP_GATE_RUNS_ON =
|
|
623
|
+
parseRecapGateRunsOn(gateRunsOn);
|
|
624
|
+
}
|
|
430
625
|
return {
|
|
431
626
|
agent,
|
|
432
627
|
appUrl,
|
|
@@ -460,7 +655,16 @@ function runSetup(args) {
|
|
|
460
655
|
appUrl: optionalArg(args, "app-url"),
|
|
461
656
|
agent: optionalArg(args, "agent"),
|
|
462
657
|
repo,
|
|
658
|
+
runsOn: optionalArg(args, "runs-on"),
|
|
659
|
+
gateRunsOn: optionalArg(args, "gate-runs-on"),
|
|
463
660
|
});
|
|
661
|
+
const runnerProblem = plan.variableProblems.find((problem) => problem.requirement.name === "VISUAL_RECAP_RUNS_ON" ||
|
|
662
|
+
problem.requirement.name === "VISUAL_RECAP_GATE_RUNS_ON");
|
|
663
|
+
if (runnerProblem) {
|
|
664
|
+
process.stderr.write(`recap setup: ${runnerProblem.reason}.\n`);
|
|
665
|
+
process.exitCode = 1;
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
464
668
|
const lines = [
|
|
465
669
|
reusable
|
|
466
670
|
? "PR Visual Recap setup (reusable workflow)"
|
|
@@ -478,6 +682,8 @@ function runSetup(args) {
|
|
|
478
682
|
force,
|
|
479
683
|
ref: optionalArg(args, "ref") ?? "main",
|
|
480
684
|
agent: plan.agent !== "claude" ? plan.agent : undefined,
|
|
685
|
+
runsOn: plan.variableValues.VISUAL_RECAP_RUNS_ON,
|
|
686
|
+
gateRunsOn: plan.variableValues.VISUAL_RECAP_GATE_RUNS_ON,
|
|
481
687
|
});
|
|
482
688
|
if (result.status === "refused") {
|
|
483
689
|
process.stderr.write(`recap setup: ${result.message}\n`);
|
|
@@ -573,6 +779,13 @@ function runDoctor(args) {
|
|
|
573
779
|
const baseDir = process.cwd();
|
|
574
780
|
const repo = resolveGithubRepo(optionalArg(args, "repo"));
|
|
575
781
|
const variables = listGithubVariables(repo);
|
|
782
|
+
if (variables && repo) {
|
|
783
|
+
const organizationVariables = listGithubOrganizationVariables(repo);
|
|
784
|
+
for (const [name, value] of organizationVariables ?? []) {
|
|
785
|
+
if (!variables.has(name))
|
|
786
|
+
variables.set(name, value);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
576
789
|
const agent = normalizeRecapAgent(optionalArg(args, "agent") ??
|
|
577
790
|
variables?.get("VISUAL_RECAP_AGENT") ??
|
|
578
791
|
process.env.VISUAL_RECAP_AGENT);
|
|
@@ -662,6 +875,66 @@ function runDoctor(args) {
|
|
|
662
875
|
lines.push(` Set it with: ${commandForMissingVariable(requirement.name, requirement.example, repo)}`);
|
|
663
876
|
}
|
|
664
877
|
}
|
|
878
|
+
const configuredRunsOn = variables.get("VISUAL_RECAP_RUNS_ON")?.trim();
|
|
879
|
+
if (!configuredRunsOn) {
|
|
880
|
+
lines.push('[ok] Recap runner: "ubuntu-latest".');
|
|
881
|
+
}
|
|
882
|
+
else {
|
|
883
|
+
const problem = recapRunsOnProblem(configuredRunsOn);
|
|
884
|
+
if (problem) {
|
|
885
|
+
ok = false;
|
|
886
|
+
lines.push(`[invalid] ${problem.reason}.`);
|
|
887
|
+
lines.push(` Set it with: ${commandForMissingVariable(RECAP_RUNS_ON_REQUIREMENT.name, RECAP_RUNS_ON_REQUIREMENT.example, repo)}`);
|
|
888
|
+
}
|
|
889
|
+
else {
|
|
890
|
+
const runsOn = parseRecapRunsOn(configuredRunsOn);
|
|
891
|
+
lines.push(`[ok] Recap runner configuration: ${runsOn.json}.`);
|
|
892
|
+
if (runsOn.selfHosted && repo) {
|
|
893
|
+
const runners = listGithubRunners(repo);
|
|
894
|
+
if (!runners) {
|
|
895
|
+
lines.push("[warn] Could not verify self-hosted runners with gh; repository Administration read access is required.");
|
|
896
|
+
}
|
|
897
|
+
else {
|
|
898
|
+
const matches = matchingRecapRunners(runners, runsOn.labels);
|
|
899
|
+
if (matches.length === 0) {
|
|
900
|
+
ok = false;
|
|
901
|
+
lines.push(`[missing] No online self-hosted runner matches: ${runsOn.labels.join(", ")}.`);
|
|
902
|
+
}
|
|
903
|
+
else {
|
|
904
|
+
lines.push(`[ok] Matching online self-hosted runner: ${matches.map((runner) => runner.name).join(", ")}.`);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
const configuredGateRunsOn = variables.get("VISUAL_RECAP_GATE_RUNS_ON")?.trim() || "ubuntu-latest";
|
|
911
|
+
const gateProblem = recapGateRunsOnProblem(configuredGateRunsOn);
|
|
912
|
+
if (gateProblem) {
|
|
913
|
+
ok = false;
|
|
914
|
+
lines.push(`[invalid] ${gateProblem.reason}.`);
|
|
915
|
+
lines.push(` Set it with: ${commandForMissingVariable(RECAP_GATE_RUNS_ON_REQUIREMENT.name, RECAP_GATE_RUNS_ON_REQUIREMENT.example, repo)}`);
|
|
916
|
+
}
|
|
917
|
+
else {
|
|
918
|
+
const gateLabel = parseRecapGateRunsOn(configuredGateRunsOn);
|
|
919
|
+
lines.push(`[ok] Gate runner label for trusted same-repo authors: ${gateLabel}.`);
|
|
920
|
+
if (!/^(?:ubuntu|windows|macos)-[A-Za-z0-9.-]+$/.test(gateLabel) &&
|
|
921
|
+
repo) {
|
|
922
|
+
const runners = listGithubRunners(repo);
|
|
923
|
+
if (!runners) {
|
|
924
|
+
lines.push("[warn] Could not verify the gate runner with gh; repository Administration read access is required.");
|
|
925
|
+
}
|
|
926
|
+
else {
|
|
927
|
+
const matches = matchingRecapRunners(runners, [gateLabel]);
|
|
928
|
+
if (matches.length === 0) {
|
|
929
|
+
ok = false;
|
|
930
|
+
lines.push(`[missing] No online self-hosted gate runner matches: ${gateLabel}.`);
|
|
931
|
+
}
|
|
932
|
+
else {
|
|
933
|
+
lines.push(`[ok] Matching online gate runner: ${matches.map((runner) => runner.name).join(", ")}.`);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
}
|
|
665
938
|
}
|
|
666
939
|
process.stdout.write(`${lines.join("\n")}\n`);
|
|
667
940
|
if (!ok)
|
|
@@ -3478,7 +3751,7 @@ function runAgentSummary(args) {
|
|
|
3478
3751
|
const HELP = `npx @agent-native/core@latest recap — PR visual recap helpers (used by the GitHub Action)
|
|
3479
3752
|
|
|
3480
3753
|
Usage:
|
|
3481
|
-
npx @agent-native/core@latest recap setup [--repo owner/name] [--agent claude|codex|openai-compatible] [--app-url <url>] [--skip-secrets] [--dry-run] [--force]
|
|
3754
|
+
npx @agent-native/core@latest recap setup [--repo owner/name] [--agent claude|codex|openai-compatible] [--app-url <url>] [--runs-on <json>] [--gate-runs-on <label>] [--skip-secrets] [--dry-run] [--force]
|
|
3482
3755
|
npx @agent-native/core@latest recap doctor [--repo owner/name] [--agent claude|codex|openai-compatible] [--app-url <url>]
|
|
3483
3756
|
npx @agent-native/core@latest recap collect-diff --base <baseSha> --head <headSha> [--out recap.diff] [--stat recap.stat]
|
|
3484
3757
|
npx @agent-native/core@latest recap block-reference [--app-url <url>] [--out recap-blocks.md]
|
|
@@ -3531,11 +3804,22 @@ Usage:
|
|
|
3531
3804
|
Write/refresh .github/workflows/pr-visual-recap.yml, then configure GitHub
|
|
3532
3805
|
Actions secrets and variables with gh when values are available from env or
|
|
3533
3806
|
the local Plans publish-token store. Missing values are printed as exact next
|
|
3534
|
-
commands; secret values are sent to gh through stdin, never argv.
|
|
3807
|
+
commands; secret values are sent to gh through stdin, never argv. Pass
|
|
3808
|
+
--runs-on '["self-hosted","linux","x64","visual-recap"]' to opt into a
|
|
3809
|
+
trusted self-hosted runner label set. In self-hosted-only repos, pass
|
|
3810
|
+
--gate-runs-on visual-recap-gate for a dedicated, preferably ephemeral,
|
|
3811
|
+
single-label gate runner. It is used only for trusted same-repo OWNER,
|
|
3812
|
+
MEMBER, or COLLABORATOR authors. The stock gate does not check out the PR
|
|
3813
|
+
tree; it evaluates workflow logic and PR metadata. Fork and untrusted PRs
|
|
3814
|
+
use GitHub-hosted ubuntu-latest instead, so they may remain unscheduled when
|
|
3815
|
+
a repository disables GitHub-hosted runners.
|
|
3535
3816
|
npx @agent-native/core@latest recap doctor
|
|
3536
3817
|
Check workflow presence/drift, local Plans publish-token availability, gh
|
|
3537
3818
|
repo access, and required GitHub Actions secrets and variables for the
|
|
3538
3819
|
selected backend, including provider-variable validity.
|
|
3820
|
+
Self-hosted runner JSON and the plain gate runner label are validated, and
|
|
3821
|
+
matching online runners are checked when the GitHub token has repository
|
|
3822
|
+
Administration read access.
|
|
3539
3823
|
`;
|
|
3540
3824
|
export async function runRecap(argv) {
|
|
3541
3825
|
const [sub, ...rest] = argv;
|