@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.
@@ -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;AA4vBjC,wBAAgB,eAAe,IAAI,MAAM,CAQxC"}
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"}
@@ -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: 52vh; }
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
- <label for="token">API bearer token</label>
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 tokenInput = document.querySelector('#token');
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
- tokenInput.value = sessionStorage.getItem('kasekiApiToken') || '';
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 = tokenInput.value.trim();
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwvBhC,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"}
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 'const fs=require("node:fs"); const input=process.argv[1]; const output=process.argv[2]; const artifact=JSON.parse(fs.readFileSync(input,"utf8")); const arrayKeys=["requirements","relevant_files","observations","plan","validation","risks"]; const invalid=[]; if (!artifact || Array.isArray(artifact) || typeof artifact !== "object") invalid.push("root"); if (typeof artifact.task !== "string" || !artifact.task.trim()) invalid.push("task"); for (const key of arrayKeys) if (!Array.isArray(artifact[key])) invalid.push(key); 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"); if (invalid.length) throw new Error("invalid scouting fields: " + invalid.join(", ")); fs.writeFileSync(output, JSON.stringify(artifact, null, 2) + "\n");' "$SCOUTING_CANDIDATE_ARTIFACT" "$SCOUTING_ARTIFACT" 2>> /results/scouting-stderr.log; then
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.43.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",