@cyanautomation/kaseki-agent 1.43.1 → 1.45.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.
- package/dist/kaseki-api-web.d.ts.map +1 -1
- package/dist/kaseki-api-web.js +111 -18
- package/dist/kaseki-api-web.js.map +1 -1
- package/kaseki-agent.sh +255 -2
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kaseki-api-web.d.ts","sourceRoot":"","sources":["../src/kaseki-api-web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"kaseki-api-web.d.ts","sourceRoot":"","sources":["../src/kaseki-api-web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAy1BjC,wBAAgB,eAAe,IAAI,MAAM,CAQxC"}
|
package/dist/kaseki-api-web.js
CHANGED
|
@@ -32,6 +32,62 @@ const controllerPage = String.raw `<!doctype html>
|
|
|
32
32
|
color: var(--ink);
|
|
33
33
|
font: 16px/1.5 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
34
34
|
}
|
|
35
|
+
.header-bar {
|
|
36
|
+
background: var(--panel);
|
|
37
|
+
border-bottom: 1px solid var(--line);
|
|
38
|
+
padding: var(--space-3) var(--space-3);
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
gap: var(--space-3);
|
|
42
|
+
justify-content: space-between;
|
|
43
|
+
}
|
|
44
|
+
.header-bar h1 {
|
|
45
|
+
margin: 0;
|
|
46
|
+
font-size: clamp(24px, 4vw, 32px);
|
|
47
|
+
line-height: 1.2;
|
|
48
|
+
}
|
|
49
|
+
.header-bar-title {
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
gap: var(--space-2);
|
|
53
|
+
}
|
|
54
|
+
.header-token-input {
|
|
55
|
+
min-width: 200px;
|
|
56
|
+
max-width: 300px;
|
|
57
|
+
width: auto;
|
|
58
|
+
padding: 10px 11px;
|
|
59
|
+
font-size: 14px;
|
|
60
|
+
}
|
|
61
|
+
@media (max-width: 767px) {
|
|
62
|
+
.header-token-input {
|
|
63
|
+
min-width: 160px;
|
|
64
|
+
max-width: 100%;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
.status-indicator {
|
|
68
|
+
display: inline-flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
justify-content: center;
|
|
71
|
+
width: 12px;
|
|
72
|
+
height: 12px;
|
|
73
|
+
border-radius: 50%;
|
|
74
|
+
background: var(--muted);
|
|
75
|
+
flex-shrink: 0;
|
|
76
|
+
}
|
|
77
|
+
.status-indicator.running {
|
|
78
|
+
background: #e8b923;
|
|
79
|
+
animation: pulse 1s ease-in-out infinite;
|
|
80
|
+
}
|
|
81
|
+
.status-indicator.completed {
|
|
82
|
+
background: var(--ok);
|
|
83
|
+
}
|
|
84
|
+
.status-indicator.failed {
|
|
85
|
+
background: var(--bad);
|
|
86
|
+
}
|
|
87
|
+
@keyframes pulse {
|
|
88
|
+
0%, 100% { opacity: 1; }
|
|
89
|
+
50% { opacity: 0.6; }
|
|
90
|
+
}
|
|
35
91
|
main {
|
|
36
92
|
display: grid;
|
|
37
93
|
gap: var(--space-4);
|
|
@@ -41,7 +97,6 @@ const controllerPage = String.raw `<!doctype html>
|
|
|
41
97
|
padding: var(--space-3);
|
|
42
98
|
}
|
|
43
99
|
h1, h2 { margin: 0; }
|
|
44
|
-
h1 { font-size: clamp(28px, 5vw, 44px); line-height: 1.12; }
|
|
45
100
|
h2 { font-size: clamp(20px, 2.2vw, 24px); line-height: 1.2; }
|
|
46
101
|
p { color: var(--muted); font-size: 16px; line-height: 1.5; margin: var(--space-1) 0 0; }
|
|
47
102
|
.panel {
|
|
@@ -178,6 +233,9 @@ const controllerPage = String.raw `<!doctype html>
|
|
|
178
233
|
#state.ok { color: var(--ok); }
|
|
179
234
|
#state.bad { color: var(--bad); }
|
|
180
235
|
@media (min-width: 768px) {
|
|
236
|
+
.header-bar {
|
|
237
|
+
padding: clamp(var(--space-3), 2vw, 24px) clamp(var(--space-3), 4vw, 48px);
|
|
238
|
+
}
|
|
181
239
|
main {
|
|
182
240
|
grid-template-columns: minmax(320px, 560px) minmax(320px, 1fr);
|
|
183
241
|
padding: clamp(var(--space-3), 4vw, 48px);
|
|
@@ -270,19 +328,23 @@ const controllerPage = String.raw `<!doctype html>
|
|
|
270
328
|
.health-check-status.bad::before { content: '✕'; }
|
|
271
329
|
@media (max-width: 767px) {
|
|
272
330
|
.action-row.run-actions > .run { order: 1; }
|
|
273
|
-
.response-panel { min-height:
|
|
331
|
+
.response-panel { min-height: 40vh; }
|
|
274
332
|
.health-checks-grid { grid-template-columns: repeat(2, 1fr); }
|
|
333
|
+
main {
|
|
334
|
+
grid-template-columns: minmax(0, 1fr);
|
|
335
|
+
}
|
|
275
336
|
}
|
|
276
337
|
</style>
|
|
277
338
|
</head>
|
|
278
339
|
<body>
|
|
340
|
+
<header class="header-bar">
|
|
341
|
+
<div class="header-bar-title">
|
|
342
|
+
<h1>Kaseki Task Console</h1>
|
|
343
|
+
<span class="status-indicator" id="header-status" data-status="idle"></span>
|
|
344
|
+
</div>
|
|
345
|
+
<input id="header-api-token" class="header-token-input" type="password" autocomplete="off" placeholder="API bearer token (required)" aria-label="API bearer token">
|
|
346
|
+
</header>
|
|
279
347
|
<main>
|
|
280
|
-
<section class="panel" aria-labelledby="page-title">
|
|
281
|
-
<header>
|
|
282
|
-
<h1 id="page-title">Kaseki Task Console</h1>
|
|
283
|
-
<p>Check system health and submit repository tasks to the Kaseki API controller.</p>
|
|
284
|
-
</header>
|
|
285
|
-
</section>
|
|
286
348
|
<section class="panel stack" aria-labelledby="tabs-heading">
|
|
287
349
|
<div class="tabs-nav" role="tablist" aria-label="Console tabs">
|
|
288
350
|
<button class="tab-button active" data-tab="health" role="tab" aria-selected="true" aria-controls="health-tab">Health</button>
|
|
@@ -332,12 +394,7 @@ const controllerPage = String.raw `<!doctype html>
|
|
|
332
394
|
<fieldset class="form-fields">
|
|
333
395
|
<legend>Required information</legend>
|
|
334
396
|
<div class="form-field">
|
|
335
|
-
|
|
336
|
-
<input id="token" name="token" type="password" autocomplete="off" placeholder="Required to submit tasks">
|
|
337
|
-
<p class="field-helper">Stored in this tab only after a successful request.</p>
|
|
338
|
-
<p class="field-error" data-error-for="token" aria-live="polite"></p>
|
|
339
|
-
</div>
|
|
340
|
-
<div class="form-field">
|
|
397
|
+
|
|
341
398
|
<label for="repo-url">Repository URL</label>
|
|
342
399
|
<input id="repo-url" name="repoUrl" type="url" required placeholder="https://github.com/org/repo">
|
|
343
400
|
<p class="field-error" data-error-for="repoUrl" aria-live="polite"></p>
|
|
@@ -427,13 +484,45 @@ const controllerPage = String.raw `<!doctype html>
|
|
|
427
484
|
const output = document.querySelector('#output');
|
|
428
485
|
const outputMeta = document.querySelector('#output-meta');
|
|
429
486
|
const state = document.querySelector('#state');
|
|
430
|
-
const
|
|
487
|
+
const headerTokenInput = document.querySelector('#header-api-token');
|
|
431
488
|
const runIdInput = document.querySelector('#run-id');
|
|
432
489
|
const runLinks = document.querySelector('#run-links');
|
|
433
490
|
const recommendedArtifacts = document.querySelector('#recommended-artifacts');
|
|
434
491
|
const recommendedArtifactLinks = document.querySelector('#recommended-artifact-links');
|
|
492
|
+
const headerStatus = document.querySelector('#header-status');
|
|
435
493
|
let pollTimer = null;
|
|
436
|
-
|
|
494
|
+
|
|
495
|
+
function getApiToken() {
|
|
496
|
+
return headerTokenInput.value.trim();
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Restore token from session storage on page load
|
|
500
|
+
headerTokenInput.value = sessionStorage.getItem('kasekiApiToken') || '';
|
|
501
|
+
|
|
502
|
+
// Save token to session storage when it changes
|
|
503
|
+
headerTokenInput.addEventListener('change', () => {
|
|
504
|
+
const token = getApiToken();
|
|
505
|
+
if (token) {
|
|
506
|
+
sessionStorage.setItem('kasekiApiToken', token);
|
|
507
|
+
} else {
|
|
508
|
+
sessionStorage.removeItem('kasekiApiToken');
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
function updateHeaderStatus(status) {
|
|
513
|
+
if (!headerStatus) return;
|
|
514
|
+
const statusMap = {
|
|
515
|
+
'idle': 'idle',
|
|
516
|
+
'running': 'running',
|
|
517
|
+
'queued': 'running',
|
|
518
|
+
'completed': 'completed',
|
|
519
|
+
'failed': 'failed',
|
|
520
|
+
'request ok': 'idle',
|
|
521
|
+
};
|
|
522
|
+
const statusClass = statusMap[status] || 'idle';
|
|
523
|
+
headerStatus.className = 'status-indicator ' + statusClass;
|
|
524
|
+
headerStatus.setAttribute('data-status', statusClass);
|
|
525
|
+
}
|
|
437
526
|
|
|
438
527
|
function sanitizeOutput(value) {
|
|
439
528
|
if (typeof value === 'string') return value;
|
|
@@ -455,6 +544,7 @@ const controllerPage = String.raw `<!doctype html>
|
|
|
455
544
|
|
|
456
545
|
function setOutputMetadata(status, runId) {
|
|
457
546
|
outputMeta.textContent = 'Status: ' + status + (runId ? ' | Run ID: ' + runId : '');
|
|
547
|
+
updateHeaderStatus(status);
|
|
458
548
|
}
|
|
459
549
|
|
|
460
550
|
function responseStatusLabel(response, payload) {
|
|
@@ -586,9 +676,9 @@ const controllerPage = String.raw `<!doctype html>
|
|
|
586
676
|
}
|
|
587
677
|
|
|
588
678
|
async function apiRequest(path, options) {
|
|
589
|
-
const token =
|
|
679
|
+
const token = getApiToken();
|
|
590
680
|
const needsAuth = options && options.auth;
|
|
591
|
-
if (needsAuth && !token) throw new Error('Enter the API bearer token first.');
|
|
681
|
+
if (needsAuth && !token) throw new Error('Enter the API bearer token in the header first.');
|
|
592
682
|
if (needsAuth && token && !isLikelyBearerToken(token)) {
|
|
593
683
|
throw new Error('Token format looks invalid. Use a plain bearer token without spaces.');
|
|
594
684
|
}
|
|
@@ -692,6 +782,9 @@ const controllerPage = String.raw `<!doctype html>
|
|
|
692
782
|
const savedTab = sessionStorage.getItem('kasekiActiveTab') || 'health';
|
|
693
783
|
const savedTabButton = document.querySelector('[data-tab="' + savedTab + '"]');
|
|
694
784
|
if (savedTabButton) savedTabButton.click();
|
|
785
|
+
|
|
786
|
+
// Initialize header status
|
|
787
|
+
updateHeaderStatus('idle');
|
|
695
788
|
|
|
696
789
|
// Health check button handlers
|
|
697
790
|
document.querySelectorAll('[data-probe]').forEach((button) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kaseki-api-web.js","sourceRoot":"","sources":["../src/kaseki-api-web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,MAAM,cAAc,GAAG,MAAM,CAAC,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"kaseki-api-web.js","sourceRoot":"","sources":["../src/kaseki-api-web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,MAAM,cAAc,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAq1BhC,CAAC;AAEF,MAAM,UAAU,eAAe;IAC7B,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;IACxB,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;QACrC,GAAG,CAAC,GAAG,CAAC,yBAAyB,EAAE,2JAA2J,CAAC,CAAC;QAChM,GAAG,CAAC,GAAG,CAAC,iBAAiB,EAAE,aAAa,CAAC,CAAC;QAC1C,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/kaseki-agent.sh
CHANGED
|
@@ -572,6 +572,151 @@ fi
|
|
|
572
572
|
# shellcheck source=scripts/allowlist-helper.sh
|
|
573
573
|
. "$ALLOWLIST_HELPER"
|
|
574
574
|
|
|
575
|
+
derive_allowlist_from_scouting() {
|
|
576
|
+
local scouting_artifact agent_patterns validation_patterns
|
|
577
|
+
scouting_artifact="${1:?missing scouting artifact path}"
|
|
578
|
+
|
|
579
|
+
if [ ! -f "$scouting_artifact" ]; then
|
|
580
|
+
printf 'derive_allowlist_from_scouting: scouting artifact not found: %s\n' "$scouting_artifact" >&2
|
|
581
|
+
return 1
|
|
582
|
+
fi
|
|
583
|
+
|
|
584
|
+
# Extract patterns from scouting.json
|
|
585
|
+
agent_patterns="$(node -e "
|
|
586
|
+
try {
|
|
587
|
+
const fs = require('node:fs');
|
|
588
|
+
const artifact = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
|
|
589
|
+
if (artifact && artifact.suggested_allowlist && Array.isArray(artifact.suggested_allowlist.agent_patterns)) {
|
|
590
|
+
console.log(artifact.suggested_allowlist.agent_patterns.join(' '));
|
|
591
|
+
}
|
|
592
|
+
} catch (e) {
|
|
593
|
+
console.error('Error parsing scouting artifact:', e.message);
|
|
594
|
+
}
|
|
595
|
+
" "$scouting_artifact" 2>/dev/null)"
|
|
596
|
+
|
|
597
|
+
validation_patterns="$(node -e "
|
|
598
|
+
try {
|
|
599
|
+
const fs = require('node:fs');
|
|
600
|
+
const artifact = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
|
|
601
|
+
if (artifact && artifact.suggested_allowlist && Array.isArray(artifact.suggested_allowlist.validation_patterns)) {
|
|
602
|
+
console.log(artifact.suggested_allowlist.validation_patterns.join(' '));
|
|
603
|
+
}
|
|
604
|
+
} catch (e) {
|
|
605
|
+
console.error('Error parsing scouting artifact:', e.message);
|
|
606
|
+
}
|
|
607
|
+
" "$scouting_artifact" 2>/dev/null)"
|
|
608
|
+
|
|
609
|
+
printf '%s\n' "$agent_patterns"
|
|
610
|
+
printf '%s\n' "$validation_patterns"
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
validate_allowlist_patterns() {
|
|
614
|
+
local patterns_str test_regex
|
|
615
|
+
patterns_str="${1:?missing patterns string}"
|
|
616
|
+
|
|
617
|
+
# Try to build a regex from the patterns - if it fails, return error
|
|
618
|
+
test_regex="$(build_allowlist_regex "$patterns_str" 2>&1)"
|
|
619
|
+
if [ -z "$test_regex" ]; then
|
|
620
|
+
# Empty patterns are valid (means no allowlist)
|
|
621
|
+
return 0
|
|
622
|
+
fi
|
|
623
|
+
|
|
624
|
+
# Test that the regex is valid by using it with grep
|
|
625
|
+
if ! printf 'test' | grep -E "^(${test_regex})$" >/dev/null 2>&1; then
|
|
626
|
+
# grep with empty patterns is valid, so this is fine
|
|
627
|
+
:
|
|
628
|
+
fi
|
|
629
|
+
return 0
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
merge_allowlists() {
|
|
633
|
+
local scouting_patterns user_patterns merged_patterns
|
|
634
|
+
scouting_patterns="${1:?missing scouting patterns}"
|
|
635
|
+
user_patterns="${2:?missing user patterns}"
|
|
636
|
+
|
|
637
|
+
# Merge patterns: if both provided, union them; otherwise use whichever is non-empty
|
|
638
|
+
if [ -n "$scouting_patterns" ] && [ -n "$user_patterns" ]; then
|
|
639
|
+
merged_patterns="$scouting_patterns $user_patterns"
|
|
640
|
+
elif [ -n "$scouting_patterns" ]; then
|
|
641
|
+
merged_patterns="$scouting_patterns"
|
|
642
|
+
elif [ -n "$user_patterns" ]; then
|
|
643
|
+
merged_patterns="$user_patterns"
|
|
644
|
+
else
|
|
645
|
+
merged_patterns=""
|
|
646
|
+
fi
|
|
647
|
+
|
|
648
|
+
printf '%s' "$merged_patterns"
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
run_scouting_allowlist_coverage() {
|
|
652
|
+
local scouting_artifact agent_patterns validation_patterns
|
|
653
|
+
scouting_artifact="${1:?missing scouting artifact path}"
|
|
654
|
+
|
|
655
|
+
if [ ! -f "$scouting_artifact" ] || [ ! -f /results/changed-files.txt ]; then
|
|
656
|
+
return 0
|
|
657
|
+
fi
|
|
658
|
+
|
|
659
|
+
agent_patterns="$(derive_allowlist_from_scouting "$scouting_artifact" | head -n 1)"
|
|
660
|
+
validation_patterns="$(derive_allowlist_from_scouting "$scouting_artifact" | tail -n 1)"
|
|
661
|
+
|
|
662
|
+
# Calculate coverage metrics using dry-run script if available
|
|
663
|
+
local agent_coverage validation_coverage agent_warnings validation_warnings
|
|
664
|
+
agent_coverage="0"
|
|
665
|
+
validation_coverage="0"
|
|
666
|
+
agent_warnings=""
|
|
667
|
+
validation_warnings=""
|
|
668
|
+
|
|
669
|
+
if [ -n "$agent_patterns" ] && command -v dry-run-allowlist.sh >/dev/null 2>&1; then
|
|
670
|
+
agent_coverage="$(dry-run-allowlist.sh --result-dir /results --allowlist "$agent_patterns" 2>/dev/null | grep -oP '(?<=Coverage: )\d+(?=%)' | head -n 1 || true)"
|
|
671
|
+
[ -z "$agent_coverage" ] && agent_coverage="0"
|
|
672
|
+
|
|
673
|
+
# Check for problematic coverage
|
|
674
|
+
if [ "$agent_coverage" -lt 30 ]; then
|
|
675
|
+
agent_warnings="patterns too narrow"
|
|
676
|
+
elif [ "$agent_coverage" -gt 98 ]; then
|
|
677
|
+
agent_warnings="patterns too broad"
|
|
678
|
+
fi
|
|
679
|
+
fi
|
|
680
|
+
|
|
681
|
+
if [ -n "$validation_patterns" ] && command -v dry-run-allowlist.sh >/dev/null 2>&1; then
|
|
682
|
+
validation_coverage="$(dry-run-allowlist.sh --result-dir /results --allowlist "$validation_patterns" 2>/dev/null | grep -oP '(?<=Coverage: )\d+(?=%)' | head -n 1 || true)"
|
|
683
|
+
[ -z "$validation_coverage" ] && validation_coverage="0"
|
|
684
|
+
|
|
685
|
+
if [ "$validation_coverage" -lt 30 ]; then
|
|
686
|
+
validation_warnings="patterns too narrow"
|
|
687
|
+
elif [ "$validation_coverage" -gt 98 ]; then
|
|
688
|
+
validation_warnings="patterns too broad"
|
|
689
|
+
fi
|
|
690
|
+
fi
|
|
691
|
+
|
|
692
|
+
# Update scouting.json with coverage metrics
|
|
693
|
+
node -e "
|
|
694
|
+
const fs = require('node:fs');
|
|
695
|
+
const artifact = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
|
|
696
|
+
artifact.coverage = {
|
|
697
|
+
agent_phase_percent: parseInt(process.argv[2]) || 0,
|
|
698
|
+
validation_phase_percent: parseInt(process.argv[3]) || 0,
|
|
699
|
+
warnings: process.argv[4] ? process.argv[4].split(',').filter(w => w) : []
|
|
700
|
+
};
|
|
701
|
+
fs.writeFileSync(process.argv[1], JSON.stringify(artifact, null, 2) + '\n');
|
|
702
|
+
" "$scouting_artifact" "$agent_coverage" "$validation_coverage" "$agent_warnings,$validation_warnings" 2>/dev/null
|
|
703
|
+
|
|
704
|
+
# Log coverage metrics
|
|
705
|
+
if [ "$agent_coverage" -ne 0 ] || [ "$validation_coverage" -ne 0 ]; then
|
|
706
|
+
{
|
|
707
|
+
printf '\n[scouting allowlist coverage]\n'
|
|
708
|
+
printf ' agent_phase: %s%% coverage\n' "$agent_coverage"
|
|
709
|
+
printf ' validation_phase: %s%% coverage\n' "$validation_coverage"
|
|
710
|
+
if [ -n "$agent_warnings" ]; then
|
|
711
|
+
printf ' ⚠ agent_phase warning: %s\n' "$agent_warnings"
|
|
712
|
+
fi
|
|
713
|
+
if [ -n "$validation_warnings" ]; then
|
|
714
|
+
printf ' ⚠ validation_phase warning: %s\n' "$validation_warnings"
|
|
715
|
+
fi
|
|
716
|
+
} | tee -a /results/scouting-report.md >> /results/quality.log
|
|
717
|
+
fi
|
|
718
|
+
}
|
|
719
|
+
|
|
575
720
|
restore_disallowed_changes() {
|
|
576
721
|
if [ "$KASEKI_RESTORE_DISALLOWED_CHANGES" != "1" ] || [ ! -d /workspace/repo/.git ]; then
|
|
577
722
|
return 0
|
|
@@ -1660,9 +1805,19 @@ The JSON object must be concise and useful to the coding agent. Use this shape:
|
|
|
1660
1805
|
"observations": ["facts learned from repository inspection"],
|
|
1661
1806
|
"plan": ["ordered coding steps"],
|
|
1662
1807
|
"validation": ["focused commands or checks to run"],
|
|
1663
|
-
"risks": ["uncertainties, edge cases, or assumptions"]
|
|
1808
|
+
"risks": ["uncertainties, edge cases, or assumptions"],
|
|
1809
|
+
"suggested_allowlist": {
|
|
1810
|
+
"agent_patterns": ["glob patterns for files the coding agent should modify"],
|
|
1811
|
+
"validation_patterns": ["glob patterns for files validation commands may touch"]
|
|
1812
|
+
}
|
|
1664
1813
|
}
|
|
1665
1814
|
|
|
1815
|
+
Guidelines for suggested_allowlist:
|
|
1816
|
+
- agent_patterns: Glob patterns narrowing which files the coding agent can modify. Use specific files (e.g., "src/parser.ts") or directories (e.g., "src/**", "tests/**"). If many related files, use broad patterns like "src/**.ts".
|
|
1817
|
+
- validation_patterns: Glob patterns for files that validation commands (npm test, npm run lint, etc.) may legitimately modify. Often identical to agent_patterns, but may differ (e.g., allow ".coverage" or "node_modules/" if generated during validation).
|
|
1818
|
+
- Both arrays can be empty if the task scope is unclear; the coding agent will work without allowlist constraints.
|
|
1819
|
+
- Prefer accurate scope over convenience: too-broad patterns defeat the purpose; too-narrow patterns will require restoration.
|
|
1820
|
+
|
|
1666
1821
|
Raw task prompt:
|
|
1667
1822
|
$TASK_PROMPT
|
|
1668
1823
|
EOF
|
|
@@ -1701,7 +1856,37 @@ run_scouting_agent() {
|
|
|
1701
1856
|
set +e
|
|
1702
1857
|
chmod -R u+w /workspace/repo 2>> /results/scouting-stderr.log || true
|
|
1703
1858
|
|
|
1704
|
-
if [ "$SCOUTING_EXIT" -eq 0 ] && ! node -e '
|
|
1859
|
+
if [ "$SCOUTING_EXIT" -eq 0 ] && ! node -e '
|
|
1860
|
+
const fs=require("node:fs");
|
|
1861
|
+
const input=process.argv[1];
|
|
1862
|
+
const output=process.argv[2];
|
|
1863
|
+
const artifact=JSON.parse(fs.readFileSync(input,"utf8"));
|
|
1864
|
+
const arrayKeys=["requirements","relevant_files","observations","plan","validation","risks"];
|
|
1865
|
+
const invalid=[];
|
|
1866
|
+
|
|
1867
|
+
if (!artifact || Array.isArray(artifact) || typeof artifact !== "object") invalid.push("root");
|
|
1868
|
+
if (typeof artifact.task !== "string" || !artifact.task.trim()) invalid.push("task");
|
|
1869
|
+
for (const key of arrayKeys) if (!Array.isArray(artifact[key])) invalid.push(key);
|
|
1870
|
+
if (Array.isArray(artifact.relevant_files) && artifact.relevant_files.some((item) => !item || typeof item.path !== "string" || typeof item.reason !== "string")) invalid.push("relevant_files entries");
|
|
1871
|
+
|
|
1872
|
+
// Validate suggested_allowlist (optional but if present, must be valid)
|
|
1873
|
+
if (artifact.suggested_allowlist) {
|
|
1874
|
+
if (typeof artifact.suggested_allowlist !== "object" || Array.isArray(artifact.suggested_allowlist)) {
|
|
1875
|
+
invalid.push("suggested_allowlist");
|
|
1876
|
+
} else {
|
|
1877
|
+
if (!Array.isArray(artifact.suggested_allowlist.agent_patterns)) invalid.push("suggested_allowlist.agent_patterns");
|
|
1878
|
+
if (!Array.isArray(artifact.suggested_allowlist.validation_patterns)) invalid.push("suggested_allowlist.validation_patterns");
|
|
1879
|
+
if (Array.isArray(artifact.suggested_allowlist.agent_patterns) && !artifact.suggested_allowlist.agent_patterns.every((p) => typeof p === "string")) invalid.push("suggested_allowlist.agent_patterns values");
|
|
1880
|
+
if (Array.isArray(artifact.suggested_allowlist.validation_patterns) && !artifact.suggested_allowlist.validation_patterns.every((p) => typeof p === "string")) invalid.push("suggested_allowlist.validation_patterns values");
|
|
1881
|
+
}
|
|
1882
|
+
} else {
|
|
1883
|
+
// Initialize empty suggested_allowlist if not provided
|
|
1884
|
+
artifact.suggested_allowlist = { agent_patterns: [], validation_patterns: [] };
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
if (invalid.length) throw new Error("invalid scouting fields: " + invalid.join(", "));
|
|
1888
|
+
fs.writeFileSync(output, JSON.stringify(artifact, null, 2) + "\n");
|
|
1889
|
+
' "$SCOUTING_CANDIDATE_ARTIFACT" "$SCOUTING_ARTIFACT" 2>> /results/scouting-stderr.log; then
|
|
1705
1890
|
SCOUTING_EXIT=86
|
|
1706
1891
|
emit_error_event "pi_scouting_artifact_invalid" "Pi scouting did not write a schema-valid JSON handoff to $SCOUTING_CANDIDATE_ARTIFACT" "exit"
|
|
1707
1892
|
fi
|
|
@@ -3317,6 +3502,74 @@ printf 'Pi version: %s\n' "$PI_VERSION"
|
|
|
3317
3502
|
if ! run_scouting_agent; then
|
|
3318
3503
|
exit 0
|
|
3319
3504
|
fi
|
|
3505
|
+
|
|
3506
|
+
# After scouting succeeds, derive and merge allowlists before main agent runs
|
|
3507
|
+
if [ "$KASEKI_SCOUTING" = "1" ] && [ -f "$SCOUTING_ARTIFACT" ]; then
|
|
3508
|
+
printf '\n==> derive allowlist from scouting\n'
|
|
3509
|
+
set_current_stage "derive allowlist from scouting"
|
|
3510
|
+
emit_progress "derive allowlist from scouting" "started"
|
|
3511
|
+
|
|
3512
|
+
scouting_agent_patterns=""
|
|
3513
|
+
scouting_validation_patterns=""
|
|
3514
|
+
allowlist_merge_status="skipped"
|
|
3515
|
+
|
|
3516
|
+
if scouting_output="$(derive_allowlist_from_scouting "$SCOUTING_ARTIFACT" 2>&1)"; then
|
|
3517
|
+
scouting_agent_patterns="$(printf '%s' "$scouting_output" | head -n 1)"
|
|
3518
|
+
scouting_validation_patterns="$(printf '%s' "$scouting_output" | tail -n 1)"
|
|
3519
|
+
|
|
3520
|
+
# Validate patterns parse correctly
|
|
3521
|
+
if validate_allowlist_patterns "$scouting_agent_patterns" && validate_allowlist_patterns "$scouting_validation_patterns"; then
|
|
3522
|
+
# Merge with user-provided allowlist
|
|
3523
|
+
user_agent_patterns="${KASEKI_CHANGED_FILES_ALLOWLIST:-}"
|
|
3524
|
+
user_validation_patterns="${KASEKI_VALIDATION_ALLOWLIST:-}"
|
|
3525
|
+
|
|
3526
|
+
merged_agent_allowlist="$(merge_allowlists "$scouting_agent_patterns" "$user_agent_patterns")"
|
|
3527
|
+
merged_validation_allowlist="$(merge_allowlists "$scouting_validation_patterns" "$user_validation_patterns")"
|
|
3528
|
+
|
|
3529
|
+
# Export merged allowlists to environment
|
|
3530
|
+
export KASEKI_CHANGED_FILES_ALLOWLIST="$merged_agent_allowlist"
|
|
3531
|
+
export KASEKI_VALIDATION_ALLOWLIST="$merged_validation_allowlist"
|
|
3532
|
+
|
|
3533
|
+
# Log merge decisions
|
|
3534
|
+
{
|
|
3535
|
+
printf '{\n'
|
|
3536
|
+
printf ' "timestamp": "%s",\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
3537
|
+
printf ' "event": "allowlist_merge",\n'
|
|
3538
|
+
printf ' "scouting_agent_patterns": "%s",\n' "$(printf '%s' "$scouting_agent_patterns" | sed 's/"/\\"/g')"
|
|
3539
|
+
printf ' "user_agent_patterns": "%s",\n' "$(printf '%s' "$user_agent_patterns" | sed 's/"/\\"/g')"
|
|
3540
|
+
printf ' "merged_agent_allowlist": "%s",\n' "$(printf '%s' "$merged_agent_allowlist" | sed 's/"/\\"/g')"
|
|
3541
|
+
printf ' "scouting_validation_patterns": "%s",\n' "$(printf '%s' "$scouting_validation_patterns" | sed 's/"/\\"/g')"
|
|
3542
|
+
printf ' "user_validation_patterns": "%s",\n' "$(printf '%s' "$user_validation_patterns" | sed 's/"/\\"/g')"
|
|
3543
|
+
printf ' "merged_validation_allowlist": "%s"\n' "$(printf '%s' "$merged_validation_allowlist" | sed 's/"/\\"/g')"
|
|
3544
|
+
printf '}\n'
|
|
3545
|
+
} | tee -a /results/metadata.jsonl
|
|
3546
|
+
|
|
3547
|
+
allowlist_merge_status="merged"
|
|
3548
|
+
|
|
3549
|
+
# Run coverage validation with dry-run
|
|
3550
|
+
if [ -s /results/changed-files.txt ]; then
|
|
3551
|
+
run_scouting_allowlist_coverage "$SCOUTING_ARTIFACT" 2>&1 | tee -a /results/quality.log
|
|
3552
|
+
fi
|
|
3553
|
+
|
|
3554
|
+
emit_progress "derive allowlist from scouting" "finished (status=$allowlist_merge_status)"
|
|
3555
|
+
else
|
|
3556
|
+
# Pattern validation failed - fail fast
|
|
3557
|
+
printf 'ERROR: Derived allowlist patterns failed validation. Cannot proceed.\n' | tee -a /results/quality.log >&2
|
|
3558
|
+
STATUS=86
|
|
3559
|
+
FAILED_COMMAND="allowlist pattern validation"
|
|
3560
|
+
emit_error_event "scouting_allowlist_invalid" "Derived allowlist patterns failed validation" "exit"
|
|
3561
|
+
exit 0
|
|
3562
|
+
fi
|
|
3563
|
+
else
|
|
3564
|
+
# Derivation failed - log and fail fast
|
|
3565
|
+
printf 'ERROR: Failed to derive allowlist from scouting artifact: %s\n' "$scouting_output" | tee -a /results/quality.log >&2
|
|
3566
|
+
STATUS=86
|
|
3567
|
+
FAILED_COMMAND="allowlist derivation from scouting"
|
|
3568
|
+
emit_error_event "scouting_allowlist_derivation_failed" "Failed to derive allowlist from scouting artifact" "exit"
|
|
3569
|
+
exit 0
|
|
3570
|
+
fi
|
|
3571
|
+
fi
|
|
3572
|
+
|
|
3320
3573
|
printf '\n==> pi coding agent\n'
|
|
3321
3574
|
set_current_stage "pi coding agent"
|
|
3322
3575
|
if [ "$KASEKI_DRY_RUN" = "1" ]; then
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyanautomation/kaseki-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.45.0",
|
|
4
4
|
"description": "Admin/helper/doctor toolbox and local API client for Kaseki diagnostics, setup, and API-backed coding-agent task workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"generate:openapi": "node scripts/generate-openapi-spec.js",
|
|
48
48
|
"build": "tsc && node dist/add-js-extensions.js && npm run generate:openapi && npm run check:dist-dynamic-imports && chmod 0755 dist/*.js",
|
|
49
49
|
"prepublishOnly": "npm run build",
|
|
50
|
-
"type-check": "tsc --noEmit",
|
|
50
|
+
"type-check": "tsc --noEmit -p tsconfig.json",
|
|
51
51
|
"check": "npm run type-check && npm run lint",
|
|
52
52
|
"test": "npm run test:unit",
|
|
53
53
|
"test:unit": "jest --passWithNoTests",
|