loki-mode 7.7.10 → 7.7.12

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/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: Multi-agent autonomous startup system. Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Requires --dangerously-skip-permissions flag.
4
4
  ---
5
5
 
6
- # Loki Mode v7.7.10
6
+ # Loki Mode v7.7.12
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -381,4 +381,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
381
381
 
382
382
  ---
383
383
 
384
- **v7.7.10 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
384
+ **v7.7.12 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.7.10
1
+ 7.7.12
package/autonomy/loki CHANGED
@@ -1470,6 +1470,26 @@ cmd_start() {
1470
1470
  # Record session start time for efficiency tracking
1471
1471
  record_session_start
1472
1472
 
1473
+ # UT2-13: Write cli-provider marker when --provider was explicitly passed.
1474
+ # This lets cmd_status_json report provider_source="cli" for the lifetime
1475
+ # of this session (24-hour heuristic). Atomic write via .tmp + mv.
1476
+ # v7.7.11 (council fix): validate provider against canonical set BEFORE
1477
+ # writing so a typo like `--provider xyz` does not pollute status output
1478
+ # with bogus state for 24h. Also write current PID alongside so the reader
1479
+ # can drop the marker when the originating process is no longer alive
1480
+ # (SIGKILL / crash paths that skip our cleanup hooks).
1481
+ case "$provider" in
1482
+ claude|codex|cline|aider)
1483
+ mkdir -p "$LOKI_DIR/state" 2>/dev/null || true
1484
+ printf '%s:%s:%s\n' "$provider" "$(date +%s)" "$$" \
1485
+ > "$LOKI_DIR/state/.cli-provider.tmp" 2>/dev/null \
1486
+ && mv "$LOKI_DIR/state/.cli-provider.tmp" \
1487
+ "$LOKI_DIR/state/cli-provider" 2>/dev/null || true
1488
+ ;;
1489
+ '') : ;; # no --provider passed
1490
+ *) log_warn "UT2-13: skipped cli-provider marker (unknown provider '$provider')" ;;
1491
+ esac
1492
+
1473
1493
  # Emit learning signal: user preference for provider selection (SYN-018)
1474
1494
  if [ -n "$provider" ]; then
1475
1495
  # User explicitly chose a provider
@@ -2272,6 +2292,41 @@ dashboard_port = sys.argv[3]
2272
2292
  env_provider = sys.argv[4]
2273
2293
  result = {}
2274
2294
 
2295
+ # UT2-13: Helper -- read cli-provider marker written by --provider flag dispatch.
2296
+ # v7.7.11 (council fix): file format is now '<provider>:<unix_ts>:<pid>' so we
2297
+ # can drop the marker when the originating process is gone (SIGKILL / crash
2298
+ # paths that bypassed our cleanup hooks). Also validates provider name against
2299
+ # canonical set so a typo writing the file can't pollute status output.
2300
+ # Legacy format '<provider>:<unix_ts>' still parses (no PID liveness check).
2301
+ # Returns (provider_name, True) when valid; (None, False) otherwise.
2302
+ def _read_cli_provider(loki_dir):
2303
+ cli_file = os.path.join(loki_dir, 'state', 'cli-provider')
2304
+ if not os.path.isfile(cli_file):
2305
+ return None, False
2306
+ try:
2307
+ content = open(cli_file).read().strip()
2308
+ parts = content.split(':')
2309
+ if len(parts) < 2:
2310
+ return None, False
2311
+ prov = parts[0]
2312
+ ts = int(parts[1])
2313
+ if prov not in ('claude', 'codex', 'cline', 'aider'):
2314
+ return None, False
2315
+ if time.time() - ts > 86400:
2316
+ return None, False
2317
+ # If a PID is present, drop the marker when that process is gone.
2318
+ if len(parts) >= 3:
2319
+ try:
2320
+ pid = int(parts[2])
2321
+ if pid > 0:
2322
+ os.kill(pid, 0)
2323
+ except (ValueError, ProcessLookupError, PermissionError):
2324
+ # PID missing/dead/cross-uid -> treat as stale.
2325
+ return None, False
2326
+ return prov, True
2327
+ except Exception:
2328
+ return None, False
2329
+
2275
2330
  # Version
2276
2331
  version_file = os.path.join(skill_dir, 'VERSION')
2277
2332
  if os.path.isfile(version_file):
@@ -2285,8 +2340,17 @@ if not os.path.isdir(loki_dir):
2285
2340
  result['status'] = 'inactive'
2286
2341
  result['phase'] = None
2287
2342
  result['iteration'] = 0
2288
- result['provider'] = env_provider if os.environ.get('LOKI_PROVIDER', '') else 'claude'
2289
- result['provider_source'] = 'env' if os.environ.get('LOKI_PROVIDER', '') else 'default'
2343
+ # UT2-13: CLI check first (inactive path -- loki_dir may not exist yet).
2344
+ _cli_prov, _cli_set = _read_cli_provider(loki_dir)
2345
+ if _cli_set:
2346
+ result['provider'] = _cli_prov
2347
+ result['provider_source'] = 'cli'
2348
+ elif os.environ.get('LOKI_PROVIDER', ''):
2349
+ result['provider'] = env_provider
2350
+ result['provider_source'] = 'env'
2351
+ else:
2352
+ result['provider'] = 'claude'
2353
+ result['provider_source'] = 'default'
2290
2354
  result['dashboard_url'] = None
2291
2355
  result['pid'] = None
2292
2356
  result['elapsed_time'] = 0
@@ -2345,7 +2409,9 @@ else:
2345
2409
  result['iteration'] = 0
2346
2410
 
2347
2411
  # Provider + provider_source (v7.7.2 B-5 clarity: surface WHY this
2348
- # value won the resolution race: 'saved' vs 'env' vs 'default').
2412
+ # value won the resolution race: 'cli' > 'saved' > 'env' > 'default').
2413
+ # UT2-13: cli source is checked FIRST -- overrides saved/env/default.
2414
+ _cli_prov, _cli_set = _read_cli_provider(loki_dir)
2349
2415
  provider_file = os.path.join(loki_dir, 'state', 'provider')
2350
2416
  if os.path.isfile(provider_file):
2351
2417
  try:
@@ -2356,7 +2422,10 @@ if os.path.isfile(provider_file):
2356
2422
  else:
2357
2423
  saved = ''
2358
2424
  env_set = os.environ.get('LOKI_PROVIDER', '') != ''
2359
- if saved:
2425
+ if _cli_set:
2426
+ result['provider'] = _cli_prov
2427
+ result['provider_source'] = 'cli'
2428
+ elif saved:
2360
2429
  result['provider'] = saved
2361
2430
  result['provider_source'] = 'saved'
2362
2431
  elif env_set:
@@ -4736,6 +4805,18 @@ cmd_run() {
4736
4805
  if [[ -n "${2:-}" ]]; then
4737
4806
  provider_override="$2"
4738
4807
  start_args+=("--provider" "$2")
4808
+ # UT2-13: Record CLI provider at parse time (atomic write).
4809
+ # v7.7.11 (council fix): validate against canonical set; include
4810
+ # PID so reader can drop the marker when originating process is gone.
4811
+ case "$2" in
4812
+ claude|codex|cline|aider)
4813
+ mkdir -p "${LOKI_DIR:-.loki}/state" 2>/dev/null || true
4814
+ printf '%s:%s:%s\n' "$2" "$(date +%s)" "$$" \
4815
+ > "${LOKI_DIR:-.loki}/state/.cli-provider.tmp" 2>/dev/null \
4816
+ && mv "${LOKI_DIR:-.loki}/state/.cli-provider.tmp" \
4817
+ "${LOKI_DIR:-.loki}/state/cli-provider" 2>/dev/null || true
4818
+ ;;
4819
+ esac
4739
4820
  shift 2
4740
4821
  else
4741
4822
  echo -e "${RED}--provider requires a value${NC}"
@@ -4745,6 +4826,17 @@ cmd_run() {
4745
4826
  --provider=*)
4746
4827
  provider_override="${1#*=}"
4747
4828
  start_args+=("--provider" "$provider_override")
4829
+ # UT2-13: Record CLI provider at parse time (atomic write).
4830
+ # v7.7.11 (council fix): validate + include PID.
4831
+ case "$provider_override" in
4832
+ claude|codex|cline|aider)
4833
+ mkdir -p "${LOKI_DIR:-.loki}/state" 2>/dev/null || true
4834
+ printf '%s:%s:%s\n' "$provider_override" "$(date +%s)" "$$" \
4835
+ > "${LOKI_DIR:-.loki}/state/.cli-provider.tmp" 2>/dev/null \
4836
+ && mv "${LOKI_DIR:-.loki}/state/.cli-provider.tmp" \
4837
+ "${LOKI_DIR:-.loki}/state/cli-provider" 2>/dev/null || true
4838
+ ;;
4839
+ esac
4748
4840
  shift
4749
4841
  ;;
4750
4842
  --parallel|--bg|--background|--simple|--complex|--no-dashboard|--sandbox|--no-plan)
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.7.10"
10
+ __version__ = "7.7.12"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -492,6 +492,24 @@
492
492
  font-size: 13px;
493
493
  color: var(--loki-text-secondary);
494
494
  }
495
+
496
+ /* USAGE.md markdown render styles */
497
+ .usage-md { max-height: 480px; overflow: auto; padding: 12px; background: var(--loki-bg-secondary, #111); border-radius: 4px; color: var(--loki-text-primary, #eee); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 13px; line-height: 1.6; }
498
+ .usage-md h1 { font-size: 1.35rem; font-weight: 600; margin: 0.75em 0 0.4em; border-bottom: 1px solid var(--loki-border, #333); padding-bottom: 4px; }
499
+ .usage-md h2 { font-size: 1.15rem; font-weight: 600; margin: 0.75em 0 0.4em; }
500
+ .usage-md h3 { font-size: 1rem; font-weight: 600; margin: 0.65em 0 0.3em; }
501
+ .usage-md p { margin: 0.4em 0; }
502
+ .usage-md ul, .usage-md ol { margin: 0.4em 0 0.4em 1.5em; padding: 0; }
503
+ .usage-md li { margin: 0.15em 0; }
504
+ .usage-md pre { background: var(--loki-bg-tertiary, #0a0a0a); border: 1px solid var(--loki-border, #333); border-radius: 4px; padding: 10px 12px; overflow-x: auto; margin: 0.5em 0; }
505
+ .usage-md pre code { font-family: 'JetBrains Mono', 'Fira Code', monospace; font-size: 11.5px; background: none; padding: 0; white-space: pre; }
506
+ .usage-md code { font-family: 'JetBrains Mono', 'Fira Code', monospace; font-size: 11.5px; background: var(--loki-bg-tertiary, #0a0a0a); padding: 1px 4px; border-radius: 3px; }
507
+ .usage-md blockquote { border-left: 3px solid var(--loki-accent, #553de9); margin: 0.5em 0; padding: 4px 12px; color: var(--loki-text-secondary, #888); }
508
+ .usage-md hr { border: none; border-top: 1px solid var(--loki-border, #333); margin: 0.75em 0; }
509
+ .usage-md strong { font-weight: 600; }
510
+ .usage-md em { font-style: italic; }
511
+ .usage-md a { color: var(--loki-accent, #553de9); text-decoration: none; }
512
+ .usage-md a:hover { text-decoration: underline; }
495
513
  </style>
496
514
  </head>
497
515
  <body>
@@ -707,10 +725,148 @@
707
725
  <h3 style="font-family: 'DM Serif Display', Georgia, serif; font-size: 1.15rem; font-weight: 400; color: var(--loki-text-primary); margin-bottom: 12px;">How to Run (USAGE.md)</h3>
708
726
  <div id="usage-doc-panel" style="background: var(--loki-bg-card, #1a1a1a); border: 1px solid var(--loki-border, #333); border-radius: 5px; padding: 12px;">
709
727
  <div id="usage-doc-meta" style="font-size: 11px; color: var(--loki-text-muted, #888); margin-bottom: 8px;">Loading...</div>
710
- <pre id="usage-doc-content" style="margin: 0; padding: 12px; background: var(--loki-bg-secondary, #111); border-radius: 4px; font-family: 'JetBrains Mono', monospace; font-size: 12px; white-space: pre-wrap; word-break: break-word; color: var(--loki-text-primary, #eee); max-height: 480px; overflow: auto;"></pre>
728
+ <div id="usage-doc-content" class="usage-md"></div>
711
729
  </div>
712
730
  <script>
713
731
  (function(){
732
+ // Minimal markdown-to-HTML converter for USAGE.md content.
733
+ // Handles: fenced code blocks, headings, horizontal rules,
734
+ // blockquotes, unordered/ordered lists, inline bold/italic/code,
735
+ // and paragraphs. Script tags are stripped from non-code text.
736
+ function renderUsageMarkdown(md) {
737
+ if (!md) return '';
738
+ var lines = md.split('
739
+ ');
740
+ var html = '';
741
+ var i = 0;
742
+ var inList = null; // 'ul' or 'ol'
743
+
744
+ function escapeHtml(s) {
745
+ return s.replace(/&/g, '&amp;')
746
+ .replace(/</g, '&lt;')
747
+ .replace(/>/g, '&gt;')
748
+ .replace(/"/g, '&quot;');
749
+ }
750
+
751
+ function closeList() {
752
+ if (inList) { html += '</' + inList + '>'; inList = null; }
753
+ }
754
+
755
+ function inlineFormat(s) {
756
+ // Escape HTML first (outside code spans)
757
+ // Process inline code spans before escaping the rest
758
+ var BT = '`'; // backtick -- avoid literal backtick inside template literal
759
+ var result = '';
760
+ var codeSpanRe = new RegExp('(' + BT + '[^' + BT + ']+' + BT + ')');
761
+ var chunks = s.split(codeSpanRe);
762
+ for (var ci = 0; ci < chunks.length; ci++) {
763
+ var ch = chunks[ci];
764
+ if (ch.charAt(0) === BT && ch.charAt(ch.length - 1) === BT && ch.length > 2) {
765
+ result += '<code>' + escapeHtml(ch.slice(1, -1)) + '</code>';
766
+ } else {
767
+ var esc = escapeHtml(ch);
768
+ // strip any residual <script tags that survived escaping (defensive)
769
+ esc = esc.replace(/&lt;script/gi, '&lt;sc​ript');
770
+ esc = esc.replace(/**([^*]+)**/g, '<strong>$1</strong>');
771
+ esc = esc.replace(/__([^_]+)__/g, '<strong>$1</strong>');
772
+ esc = esc.replace(/*([^*]+)*/g, '<em>$1</em>');
773
+ esc = esc.replace(/_([^_]+)_/g, '<em>$1</em>');
774
+ // v7.7.11 XSS guard (Opus 1 council): only allow http/https/mailto/anchor/relative href.
775
+ // javascript: / data: / vbscript: are stripped to a plain code span so the URL stays visible
776
+ // but is not clickable. USAGE.md absorbs agent output + PRD text; treat as untrusted.
777
+ esc = esc.replace(/[([^]]+)](([^)]+))/g, function(_m, label, url){
778
+ var safe = /^(https?://|mailto:|#|/(?!/))/.test(url);
779
+ if (safe) return '<a href="' + url + '" rel="noopener noreferrer">' + label + '</a>';
780
+ return '<code>' + label + ' (' + url + ')</code>';
781
+ });
782
+ result += esc;
783
+ }
784
+ }
785
+ return result;
786
+ }
787
+
788
+ while (i < lines.length) {
789
+ var line = lines[i];
790
+
791
+ // Fenced code block (BT3 = three backticks)
792
+ var BT3 = '```';
793
+ if (line.slice(0, 3) === BT3) {
794
+ closeList();
795
+ var lang = line.slice(3).trim();
796
+ var codeLines = [];
797
+ i++;
798
+ while (i < lines.length && lines[i].slice(0, 3) !== BT3) {
799
+ codeLines.push(lines[i]);
800
+ i++;
801
+ }
802
+ var codeContent = escapeHtml(codeLines.join('
803
+ '));
804
+ html += '<pre><code' + (lang ? ' class="language-' + escapeHtml(lang) + '"' : '') + '>' + codeContent + '</code></pre>';
805
+ i++;
806
+ continue;
807
+ }
808
+
809
+ // Headings
810
+ var hMatch = line.match(/^(#{1,6})s+(.*)/);
811
+ if (hMatch) {
812
+ closeList();
813
+ var level = hMatch[1].length;
814
+ html += '<h' + level + '>' + inlineFormat(hMatch[2]) + '</h' + level + '>';
815
+ i++;
816
+ continue;
817
+ }
818
+
819
+ // Horizontal rule
820
+ if (/^(-{3,}|*{3,}|_{3,})$/.test(line.trim())) {
821
+ closeList();
822
+ html += '<hr>';
823
+ i++;
824
+ continue;
825
+ }
826
+
827
+ // Blockquote
828
+ if (/^>/.test(line)) {
829
+ closeList();
830
+ html += '<blockquote>' + inlineFormat(line.replace(/^>s?/, '')) + '</blockquote>';
831
+ i++;
832
+ continue;
833
+ }
834
+
835
+ // Unordered list
836
+ var ulMatch = line.match(/^(s*[-*+])s+(.*)/);
837
+ if (ulMatch) {
838
+ if (inList !== 'ul') { closeList(); html += '<ul>'; inList = 'ul'; }
839
+ html += '<li>' + inlineFormat(ulMatch[2]) + '</li>';
840
+ i++;
841
+ continue;
842
+ }
843
+
844
+ // Ordered list
845
+ var olMatch = line.match(/^s*d+.s+(.*)/);
846
+ if (olMatch) {
847
+ if (inList !== 'ol') { closeList(); html += '<ol>'; inList = 'ol'; }
848
+ html += '<li>' + inlineFormat(olMatch[1]) + '</li>';
849
+ i++;
850
+ continue;
851
+ }
852
+
853
+ // Blank line
854
+ if (line.trim() === '') {
855
+ closeList();
856
+ i++;
857
+ continue;
858
+ }
859
+
860
+ // Paragraph
861
+ closeList();
862
+ html += '<p>' + inlineFormat(line) + '</p>';
863
+ i++;
864
+ }
865
+
866
+ closeList();
867
+ return html;
868
+ }
869
+
714
870
  function loadUsage(){
715
871
  fetch('/api/usage').then(function(r){ return r.json(); }).then(function(j){
716
872
  var meta = document.getElementById('usage-doc-meta');
@@ -718,13 +874,13 @@
718
874
  if (!meta || !body) return;
719
875
  if (!j || !j.exists){
720
876
  meta.textContent = 'USAGE.md not generated yet. Loki writes it at the end of each session.';
721
- body.textContent = '';
877
+ body.innerHTML = '';
722
878
  return;
723
879
  }
724
880
  var size = j.size > 1024 ? (j.size/1024).toFixed(1) + ' KB' : j.size + ' B';
725
881
  var when = j.mtime ? new Date(j.mtime*1000).toLocaleString() : '';
726
882
  meta.textContent = j.path + ' (' + size + (j.truncated ? ', truncated' : '') + ', ' + when + ')';
727
- body.textContent = j.content || '';
883
+ body.innerHTML = renderUsageMarkdown(j.content || '');
728
884
  }).catch(function(e){
729
885
  var meta = document.getElementById('usage-doc-meta');
730
886
  if (meta) meta.textContent = 'Failed to load USAGE.md: ' + (e && e.message ? e.message : 'unknown');
@@ -2,7 +2,7 @@
2
2
 
3
3
  The flagship product of [Autonomi](https://www.autonomi.dev/). Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v7.7.10
5
+ **Version:** v7.7.12
6
6
 
7
7
  ---
8
8