instar 0.27.2 → 0.28.1
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/.claude/skills/build/SKILL.md +268 -0
- package/README.md +2 -1
- package/dashboard/index.html +501 -491
- package/dist/cli.js +81 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +17 -10
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/listener.d.ts +46 -0
- package/dist/commands/listener.d.ts.map +1 -0
- package/dist/commands/listener.js +467 -0
- package/dist/commands/listener.js.map +1 -0
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +101 -3
- package/dist/commands/server.js.map +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +84 -21
- package/dist/commands/setup.js.map +1 -1
- package/dist/core/AgentRegistry.d.ts.map +1 -1
- package/dist/core/AgentRegistry.js +30 -2
- package/dist/core/AgentRegistry.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +2 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +29 -28
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/types.d.ts +33 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/lifeline/TelegramLifeline.d.ts.map +1 -1
- package/dist/lifeline/TelegramLifeline.js +10 -2
- package/dist/lifeline/TelegramLifeline.js.map +1 -1
- package/dist/scheduler/JobScheduler.d.ts.map +1 -1
- package/dist/scheduler/JobScheduler.js +8 -4
- package/dist/scheduler/JobScheduler.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +87 -0
- package/dist/server/routes.js.map +1 -1
- package/dist/threadline/PipeSessionSpawner.d.ts +123 -0
- package/dist/threadline/PipeSessionSpawner.d.ts.map +1 -0
- package/dist/threadline/PipeSessionSpawner.js +343 -0
- package/dist/threadline/PipeSessionSpawner.js.map +1 -0
- package/dist/threadline/ThreadResumeMap.d.ts +22 -0
- package/dist/threadline/ThreadResumeMap.d.ts.map +1 -1
- package/dist/threadline/ThreadResumeMap.js +37 -0
- package/dist/threadline/ThreadResumeMap.js.map +1 -1
- package/dist/threadline/ThreadlineBootstrap.d.ts.map +1 -1
- package/dist/threadline/ThreadlineBootstrap.js +155 -72
- package/dist/threadline/ThreadlineBootstrap.js.map +1 -1
- package/dist/threadline/WakeSocketServer.d.ts +49 -0
- package/dist/threadline/WakeSocketServer.d.ts.map +1 -0
- package/dist/threadline/WakeSocketServer.js +115 -0
- package/dist/threadline/WakeSocketServer.js.map +1 -0
- package/dist/threadline/listener-daemon.d.ts +94 -0
- package/dist/threadline/listener-daemon.d.ts.map +1 -0
- package/dist/threadline/listener-daemon.js +550 -0
- package/dist/threadline/listener-daemon.js.map +1 -0
- package/package.json +2 -1
- package/src/data/builtin-manifest.json +118 -118
- package/upgrades/0.28.0.md +54 -0
- package/upgrades/0.28.1.md +24 -0
- package/upgrades/NEXT.md +35 -0
- /package/.claude/skills/autonomous/{skill.md → SKILL.md} +0 -0
- /package/.claude/skills/secret-setup/{skill.md → SKILL.md} +0 -0
- /package/.claude/skills/setup-wizard/{skill.md → SKILL.md} +0 -0
package/dashboard/index.html
CHANGED
|
@@ -1555,6 +1555,24 @@
|
|
|
1555
1555
|
}
|
|
1556
1556
|
|
|
1557
1557
|
/* ── Jobs Tab ─────────────────────────────────────────── */
|
|
1558
|
+
.jobs-summary { display: flex; gap: 6px; padding: 0 12px 8px; flex-wrap: wrap; }
|
|
1559
|
+
.jobs-summary-card { flex: 1; min-width: 50px; text-align: center; padding: 6px 4px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; }
|
|
1560
|
+
.jobs-summary-val { font-size: 16px; font-weight: 700; color: var(--text-bright); }
|
|
1561
|
+
.jobs-summary-label { font-size: 9px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.3px; }
|
|
1562
|
+
.jobs-summary-card.failing .jobs-summary-val { color: var(--red); }
|
|
1563
|
+
.jobs-summary-card.running .jobs-summary-val { color: var(--blue); }
|
|
1564
|
+
.sparkline-legend { display: flex; gap: 10px; padding: 4px 0 8px; font-size: 10px; color: var(--text-dim); }
|
|
1565
|
+
.sparkline-legend-item { display: flex; align-items: center; gap: 4px; }
|
|
1566
|
+
.sparkline-legend-dot { width: 8px; height: 8px; border-radius: 2px; }
|
|
1567
|
+
.sparkline-legend-dot.success { background: var(--accent); }
|
|
1568
|
+
.sparkline-legend-dot.failure { background: var(--red); }
|
|
1569
|
+
.sparkline-legend-dot.skipped { background: #555; }
|
|
1570
|
+
.job-config-section { margin-top: 16px; border-top: 1px solid var(--border); padding-top: 12px; }
|
|
1571
|
+
.job-config-title { font-size: 11px; font-weight: 600; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 10px; cursor: pointer; display: flex; align-items: center; gap: 6px; }
|
|
1572
|
+
.job-config-title:hover { color: var(--text); }
|
|
1573
|
+
.job-config-grid { display: grid; grid-template-columns: 120px 1fr; gap: 4px 12px; font-size: 12px; }
|
|
1574
|
+
.job-config-key { color: var(--text-dim); padding: 4px 0; }
|
|
1575
|
+
.job-config-val { color: var(--text); padding: 4px 0; word-break: break-word; }
|
|
1558
1576
|
.jobs-container {
|
|
1559
1577
|
display: flex;
|
|
1560
1578
|
grid-column: 1 / -1;
|
|
@@ -1929,7 +1947,99 @@
|
|
|
1929
1947
|
.spark.s-pending { background: var(--blue); }
|
|
1930
1948
|
.spark.s-skipped { background: #333; }
|
|
1931
1949
|
|
|
1932
|
-
/* ──
|
|
1950
|
+
/* ── Features Tab (was Discovery) ──────────────────────────── */
|
|
1951
|
+
.features-container {
|
|
1952
|
+
grid-column: 1 / -1;
|
|
1953
|
+
overflow-y: auto;
|
|
1954
|
+
background: var(--bg);
|
|
1955
|
+
padding: 20px;
|
|
1956
|
+
}
|
|
1957
|
+
.features-main { max-width: 960px; margin: 0 auto; }
|
|
1958
|
+
.features-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
|
|
1959
|
+
.features-header h2 { font-size: 16px; font-weight: 600; color: var(--text-bright); }
|
|
1960
|
+
.features-subtitle { font-size: 12px; color: var(--text-dim); margin-bottom: 20px; line-height: 1.4; }
|
|
1961
|
+
.features-refresh { font-size: 11px; padding: 4px 12px; border-radius: 4px; border: 1px solid var(--border); background: transparent; color: var(--text-dim); cursor: pointer; }
|
|
1962
|
+
.features-refresh:hover { border-color: var(--text-dim); color: var(--text); }
|
|
1963
|
+
|
|
1964
|
+
/* Autonomy Profile Card */
|
|
1965
|
+
.autonomy-card {
|
|
1966
|
+
background: var(--bg-panel);
|
|
1967
|
+
border: 1px solid var(--border);
|
|
1968
|
+
border-radius: 10px;
|
|
1969
|
+
padding: 18px 22px;
|
|
1970
|
+
margin-bottom: 24px;
|
|
1971
|
+
display: flex;
|
|
1972
|
+
align-items: center;
|
|
1973
|
+
gap: 18px;
|
|
1974
|
+
}
|
|
1975
|
+
.autonomy-card-icon { font-size: 28px; flex-shrink: 0; }
|
|
1976
|
+
.autonomy-card-info { flex: 1; }
|
|
1977
|
+
.autonomy-card-label { font-size: 11px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; }
|
|
1978
|
+
.autonomy-card-level { font-size: 16px; font-weight: 600; color: var(--text-bright); margin-bottom: 4px; text-transform: capitalize; }
|
|
1979
|
+
.autonomy-card-desc { font-size: 12px; color: var(--text-dim); line-height: 1.4; }
|
|
1980
|
+
.autonomy-card-btn { font-size: 11px; padding: 6px 16px; border-radius: 6px; border: 1px solid var(--border); background: transparent; color: var(--text); cursor: pointer; white-space: nowrap; }
|
|
1981
|
+
.autonomy-card-btn:hover { border-color: var(--accent); color: var(--accent); }
|
|
1982
|
+
|
|
1983
|
+
/* Profile Selector Modal */
|
|
1984
|
+
.profile-selector { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin: 16px 0; }
|
|
1985
|
+
.profile-option { background: var(--bg-panel); border: 1px solid var(--border); border-radius: 8px; padding: 14px 16px; cursor: pointer; transition: border-color 0.15s; }
|
|
1986
|
+
.profile-option:hover { border-color: rgba(255,255,255,0.2); }
|
|
1987
|
+
.profile-option.selected { border-color: var(--accent); background: rgba(74,222,128,0.05); }
|
|
1988
|
+
.profile-option-name { font-size: 14px; font-weight: 600; color: var(--text-bright); margin-bottom: 4px; text-transform: capitalize; }
|
|
1989
|
+
.profile-option-desc { font-size: 11px; color: var(--text-dim); line-height: 1.4; }
|
|
1990
|
+
.profile-option-badge { display: inline-block; font-size: 9px; padding: 1px 6px; border-radius: 3px; margin-top: 6px; }
|
|
1991
|
+
.profile-option-badge.recommended { background: rgba(74,222,128,0.15); color: var(--accent); }
|
|
1992
|
+
|
|
1993
|
+
/* Feature Category Section */
|
|
1994
|
+
.feat-category { margin-bottom: 24px; }
|
|
1995
|
+
.feat-category-title { font-size: 11px; font-weight: 600; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 10px; padding-bottom: 6px; border-bottom: 1px solid var(--border); }
|
|
1996
|
+
.feat-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 10px; }
|
|
1997
|
+
|
|
1998
|
+
/* Feature Card */
|
|
1999
|
+
.feat-card {
|
|
2000
|
+
background: var(--bg-panel);
|
|
2001
|
+
border: 1px solid var(--border);
|
|
2002
|
+
border-radius: 10px;
|
|
2003
|
+
padding: 14px 16px;
|
|
2004
|
+
display: flex;
|
|
2005
|
+
flex-direction: column;
|
|
2006
|
+
gap: 8px;
|
|
2007
|
+
cursor: pointer;
|
|
2008
|
+
transition: border-color 0.15s, transform 0.1s;
|
|
2009
|
+
}
|
|
2010
|
+
.feat-card:hover { border-color: rgba(255,255,255,0.15); transform: translateY(-1px); }
|
|
2011
|
+
.feat-card-top { display: flex; align-items: center; gap: 10px; }
|
|
2012
|
+
.feat-card-name { font-size: 14px; font-weight: 600; color: var(--text-bright); flex: 1; }
|
|
2013
|
+
.feat-card-arrow { font-size: 11px; color: var(--text-dim); }
|
|
2014
|
+
.feat-card-desc { font-size: 12px; color: var(--text-dim); line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
|
2015
|
+
|
|
2016
|
+
/* Feature Toggle */
|
|
2017
|
+
.feat-toggle { position: relative; display: inline-block; width: 36px; height: 20px; flex-shrink: 0; }
|
|
2018
|
+
.feat-toggle input { opacity: 0; width: 0; height: 0; }
|
|
2019
|
+
.feat-toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background: #333; border-radius: 20px; transition: 0.2s; }
|
|
2020
|
+
.feat-toggle-slider:before { position: absolute; content: ""; height: 14px; width: 14px; left: 3px; bottom: 3px; background: #888; border-radius: 50%; transition: 0.2s; }
|
|
2021
|
+
.feat-toggle input:checked + .feat-toggle-slider { background: rgba(74,222,128,0.3); }
|
|
2022
|
+
.feat-toggle input:checked + .feat-toggle-slider:before { transform: translateX(16px); background: var(--accent); }
|
|
2023
|
+
|
|
2024
|
+
/* Feature Detail View */
|
|
2025
|
+
.feat-detail-view { display: none; }
|
|
2026
|
+
.feat-detail-view.active { display: block; }
|
|
2027
|
+
.feat-detail-back { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-dim); cursor: pointer; border: none; background: none; padding: 0; margin-bottom: 16px; }
|
|
2028
|
+
.feat-detail-back:hover { color: var(--text); }
|
|
2029
|
+
.feat-detail-header { margin-bottom: 20px; }
|
|
2030
|
+
.feat-detail-title { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
|
|
2031
|
+
.feat-detail-title h3 { font-size: 18px; font-weight: 600; color: var(--text-bright); margin: 0; flex: 1; }
|
|
2032
|
+
.feat-detail-category { font-size: 10px; padding: 2px 8px; border-radius: 3px; background: rgba(255,255,255,0.06); color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.5px; }
|
|
2033
|
+
.feat-detail-description { font-size: 13px; color: var(--text-dim); line-height: 1.6; max-width: 700px; margin-bottom: 20px; }
|
|
2034
|
+
.feat-detail-section { margin-bottom: 20px; }
|
|
2035
|
+
.feat-detail-section-title { font-size: 11px; font-weight: 600; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 10px; padding-bottom: 6px; border-bottom: 1px solid var(--border); }
|
|
2036
|
+
.feat-data-item { display: flex; gap: 12px; padding: 10px 14px; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 6px; margin-bottom: 6px; font-size: 12px; }
|
|
2037
|
+
.feat-data-icon { font-size: 14px; flex-shrink: 0; margin-top: 1px; }
|
|
2038
|
+
.feat-data-content { flex: 1; }
|
|
2039
|
+
.feat-data-label { font-weight: 500; color: var(--text-bright); margin-bottom: 2px; }
|
|
2040
|
+
.feat-data-desc { color: var(--text-dim); font-size: 11px; line-height: 1.4; }
|
|
2041
|
+
.feat-reversibility { font-size: 12px; color: var(--text-dim); line-height: 1.5; padding: 12px 14px; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 6px; }
|
|
2042
|
+
|
|
1933
2043
|
/* ── Systems Tab ──────────────────────────────────────────── */
|
|
1934
2044
|
.systems-container { grid-column: 1 / -1; overflow-y: auto; background: var(--bg); padding: 20px; }
|
|
1935
2045
|
.systems-main { max-width: 960px; margin: 0 auto; }
|
|
@@ -2026,250 +2136,7 @@
|
|
|
2026
2136
|
.cap-content-empty { padding: 16px; text-align: center; color: var(--text-dim); font-size: 12px; }
|
|
2027
2137
|
.cap-stat-card:hover { border-color: var(--accent); transition: border-color 0.15s; }
|
|
2028
2138
|
|
|
2029
|
-
|
|
2030
|
-
grid-column: 1 / -1;
|
|
2031
|
-
overflow-y: auto;
|
|
2032
|
-
background: var(--bg);
|
|
2033
|
-
padding: 20px;
|
|
2034
|
-
}
|
|
2035
|
-
|
|
2036
|
-
.discovery-main {
|
|
2037
|
-
max-width: 900px;
|
|
2038
|
-
margin: 0 auto;
|
|
2039
|
-
}
|
|
2040
|
-
|
|
2041
|
-
.discovery-header {
|
|
2042
|
-
display: flex;
|
|
2043
|
-
align-items: center;
|
|
2044
|
-
justify-content: space-between;
|
|
2045
|
-
margin-bottom: 20px;
|
|
2046
|
-
}
|
|
2047
|
-
|
|
2048
|
-
.discovery-header h2 {
|
|
2049
|
-
font-size: 16px;
|
|
2050
|
-
font-weight: 600;
|
|
2051
|
-
color: var(--text-bright);
|
|
2052
|
-
}
|
|
2053
|
-
|
|
2054
|
-
.discovery-refresh {
|
|
2055
|
-
font-size: 11px;
|
|
2056
|
-
padding: 4px 12px;
|
|
2057
|
-
border-radius: 4px;
|
|
2058
|
-
border: 1px solid var(--border);
|
|
2059
|
-
background: transparent;
|
|
2060
|
-
color: var(--text-dim);
|
|
2061
|
-
cursor: pointer;
|
|
2062
|
-
}
|
|
2063
|
-
|
|
2064
|
-
.discovery-refresh:hover {
|
|
2065
|
-
border-color: var(--text-dim);
|
|
2066
|
-
color: var(--text);
|
|
2067
|
-
}
|
|
2068
|
-
|
|
2069
|
-
.discovery-section {
|
|
2070
|
-
margin-bottom: 24px;
|
|
2071
|
-
}
|
|
2072
|
-
|
|
2073
|
-
.discovery-section h3 {
|
|
2074
|
-
font-size: 13px;
|
|
2075
|
-
font-weight: 600;
|
|
2076
|
-
color: var(--text-bright);
|
|
2077
|
-
margin-bottom: 12px;
|
|
2078
|
-
}
|
|
2079
|
-
|
|
2080
|
-
.funnel-chart {
|
|
2081
|
-
display: flex;
|
|
2082
|
-
gap: 4px;
|
|
2083
|
-
align-items: flex-end;
|
|
2084
|
-
height: 120px;
|
|
2085
|
-
padding: 12px 16px;
|
|
2086
|
-
background: var(--bg-panel);
|
|
2087
|
-
border: 1px solid var(--border);
|
|
2088
|
-
border-radius: 8px;
|
|
2089
|
-
}
|
|
2090
|
-
|
|
2091
|
-
.funnel-bar {
|
|
2092
|
-
flex: 1;
|
|
2093
|
-
display: flex;
|
|
2094
|
-
flex-direction: column;
|
|
2095
|
-
align-items: center;
|
|
2096
|
-
gap: 4px;
|
|
2097
|
-
}
|
|
2098
|
-
|
|
2099
|
-
.funnel-bar-fill {
|
|
2100
|
-
width: 100%;
|
|
2101
|
-
border-radius: 3px 3px 0 0;
|
|
2102
|
-
min-height: 2px;
|
|
2103
|
-
transition: height 0.3s;
|
|
2104
|
-
}
|
|
2105
|
-
|
|
2106
|
-
.funnel-bar-label {
|
|
2107
|
-
font-size: 10px;
|
|
2108
|
-
color: var(--text-dim);
|
|
2109
|
-
text-align: center;
|
|
2110
|
-
white-space: nowrap;
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
.funnel-bar-count {
|
|
2114
|
-
font-size: 12px;
|
|
2115
|
-
font-weight: 600;
|
|
2116
|
-
color: var(--text-bright);
|
|
2117
|
-
}
|
|
2118
|
-
|
|
2119
|
-
.discovery-filter-bar {
|
|
2120
|
-
display: flex;
|
|
2121
|
-
gap: 4px;
|
|
2122
|
-
margin-bottom: 12px;
|
|
2123
|
-
flex-wrap: wrap;
|
|
2124
|
-
}
|
|
2125
|
-
|
|
2126
|
-
.discovery-filter {
|
|
2127
|
-
font-size: 11px;
|
|
2128
|
-
padding: 2px 8px;
|
|
2129
|
-
border-radius: 10px;
|
|
2130
|
-
border: 1px solid var(--border);
|
|
2131
|
-
background: transparent;
|
|
2132
|
-
color: var(--text-dim);
|
|
2133
|
-
cursor: pointer;
|
|
2134
|
-
transition: all 0.15s;
|
|
2135
|
-
}
|
|
2136
|
-
|
|
2137
|
-
.discovery-filter:hover {
|
|
2138
|
-
border-color: var(--text-dim);
|
|
2139
|
-
color: var(--text);
|
|
2140
|
-
}
|
|
2141
|
-
|
|
2142
|
-
.discovery-filter.active {
|
|
2143
|
-
background: var(--accent-dim);
|
|
2144
|
-
border-color: var(--accent-dim);
|
|
2145
|
-
color: #000;
|
|
2146
|
-
}
|
|
2147
|
-
|
|
2148
|
-
.feature-grid {
|
|
2149
|
-
display: grid;
|
|
2150
|
-
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
|
2151
|
-
gap: 10px;
|
|
2152
|
-
}
|
|
2153
|
-
|
|
2154
|
-
.feature-card {
|
|
2155
|
-
background: var(--bg-panel);
|
|
2156
|
-
border: 1px solid var(--border);
|
|
2157
|
-
border-radius: 8px;
|
|
2158
|
-
padding: 12px 14px;
|
|
2159
|
-
font-size: 12px;
|
|
2160
|
-
}
|
|
2161
|
-
|
|
2162
|
-
.feature-card-top {
|
|
2163
|
-
display: flex;
|
|
2164
|
-
align-items: center;
|
|
2165
|
-
gap: 8px;
|
|
2166
|
-
margin-bottom: 6px;
|
|
2167
|
-
}
|
|
2168
|
-
|
|
2169
|
-
.feature-card-name {
|
|
2170
|
-
font-weight: 500;
|
|
2171
|
-
color: var(--text-bright);
|
|
2172
|
-
flex: 1;
|
|
2173
|
-
}
|
|
2174
|
-
|
|
2175
|
-
.feature-state-badge {
|
|
2176
|
-
font-size: 10px;
|
|
2177
|
-
padding: 1px 6px;
|
|
2178
|
-
border-radius: 3px;
|
|
2179
|
-
font-weight: 600;
|
|
2180
|
-
}
|
|
2181
|
-
|
|
2182
|
-
.feature-state-badge.enabled { background: #0f2f0f; color: var(--accent); }
|
|
2183
|
-
.feature-state-badge.undiscovered { background: #1a1a1a; color: #666; }
|
|
2184
|
-
.feature-state-badge.aware { background: #0f1f2f; color: var(--blue); }
|
|
2185
|
-
.feature-state-badge.interested { background: #1f1f0f; color: #eab308; }
|
|
2186
|
-
.feature-state-badge.deferred { background: #1a1a1a; color: #888; }
|
|
2187
|
-
.feature-state-badge.declined { background: #2f0f0f; color: var(--red); }
|
|
2188
|
-
.feature-state-badge.disabled { background: #1a1a1a; color: #555; }
|
|
2189
|
-
|
|
2190
|
-
.feature-card-desc {
|
|
2191
|
-
color: var(--text-dim);
|
|
2192
|
-
font-size: 11px;
|
|
2193
|
-
line-height: 1.4;
|
|
2194
|
-
margin-bottom: 4px;
|
|
2195
|
-
}
|
|
2196
|
-
|
|
2197
|
-
.feature-card-meta {
|
|
2198
|
-
display: flex;
|
|
2199
|
-
gap: 8px;
|
|
2200
|
-
color: var(--text-dim);
|
|
2201
|
-
font-size: 10px;
|
|
2202
|
-
}
|
|
2203
|
-
|
|
2204
|
-
.feature-card-meta .tier { text-transform: uppercase; letter-spacing: 0.5px; }
|
|
2205
|
-
.feature-card-meta .cooldown { color: var(--orange); }
|
|
2206
|
-
|
|
2207
|
-
.event-log {
|
|
2208
|
-
background: var(--bg-panel);
|
|
2209
|
-
border: 1px solid var(--border);
|
|
2210
|
-
border-radius: 8px;
|
|
2211
|
-
max-height: 300px;
|
|
2212
|
-
overflow-y: auto;
|
|
2213
|
-
}
|
|
2214
|
-
|
|
2215
|
-
.event-row {
|
|
2216
|
-
display: flex;
|
|
2217
|
-
gap: 10px;
|
|
2218
|
-
padding: 8px 14px;
|
|
2219
|
-
border-bottom: 1px solid var(--border);
|
|
2220
|
-
font-size: 11px;
|
|
2221
|
-
align-items: center;
|
|
2222
|
-
}
|
|
2223
|
-
|
|
2224
|
-
.event-row:last-child { border-bottom: none; }
|
|
2225
|
-
|
|
2226
|
-
.event-time {
|
|
2227
|
-
color: var(--text-dim);
|
|
2228
|
-
flex-shrink: 0;
|
|
2229
|
-
width: 70px;
|
|
2230
|
-
}
|
|
2231
|
-
|
|
2232
|
-
.event-feature {
|
|
2233
|
-
color: var(--text-bright);
|
|
2234
|
-
font-weight: 500;
|
|
2235
|
-
flex-shrink: 0;
|
|
2236
|
-
width: 140px;
|
|
2237
|
-
}
|
|
2238
|
-
|
|
2239
|
-
.event-transition {
|
|
2240
|
-
color: var(--text);
|
|
2241
|
-
flex: 1;
|
|
2242
|
-
}
|
|
2243
|
-
|
|
2244
|
-
.event-arrow { color: var(--text-dim); }
|
|
2245
|
-
|
|
2246
|
-
.digest-item {
|
|
2247
|
-
background: var(--bg-panel);
|
|
2248
|
-
border: 1px solid var(--border);
|
|
2249
|
-
border-radius: 6px;
|
|
2250
|
-
padding: 10px 14px;
|
|
2251
|
-
margin-bottom: 8px;
|
|
2252
|
-
font-size: 12px;
|
|
2253
|
-
}
|
|
2254
|
-
|
|
2255
|
-
.digest-item-title {
|
|
2256
|
-
color: var(--text-bright);
|
|
2257
|
-
font-weight: 500;
|
|
2258
|
-
margin-bottom: 2px;
|
|
2259
|
-
}
|
|
2260
|
-
|
|
2261
|
-
.digest-item-desc {
|
|
2262
|
-
color: var(--text-dim);
|
|
2263
|
-
font-size: 11px;
|
|
2264
|
-
}
|
|
2265
|
-
|
|
2266
|
-
/* Discovery metrics summary */
|
|
2267
|
-
.discovery-metrics {
|
|
2268
|
-
display: flex;
|
|
2269
|
-
gap: 12px;
|
|
2270
|
-
margin-bottom: 16px;
|
|
2271
|
-
flex-wrap: wrap;
|
|
2272
|
-
}
|
|
2139
|
+
/* (Legacy discovery styles removed — replaced by Features tab above) */
|
|
2273
2140
|
|
|
2274
2141
|
.metric-card {
|
|
2275
2142
|
flex: 1;
|
|
@@ -2542,10 +2409,10 @@
|
|
|
2542
2409
|
<nav class="tab-bar">
|
|
2543
2410
|
<button class="tab active" data-tab="sessions" onclick="switchTab('sessions')">Sessions <span class="tab-count" id="tabSessionCount">0</span></button>
|
|
2544
2411
|
<button class="tab" data-tab="files" onclick="switchTab('files')">Files</button>
|
|
2545
|
-
<button class="tab" data-tab="dropzone" onclick="switchTab('dropzone')">
|
|
2412
|
+
<button class="tab" data-tab="dropzone" onclick="switchTab('dropzone')">Send Content</button>
|
|
2546
2413
|
<button class="tab" data-tab="jobs" onclick="switchTab('jobs')">Jobs <span class="tab-count" id="tabJobCount">0</span></button>
|
|
2547
|
-
<button class="tab" data-tab="
|
|
2548
|
-
<button class="tab" data-tab="systems" onclick="switchTab('systems')">
|
|
2414
|
+
<button class="tab" data-tab="features" onclick="switchTab('features')">Features</button>
|
|
2415
|
+
<button class="tab" data-tab="systems" onclick="switchTab('systems')">Health</button>
|
|
2549
2416
|
</nav>
|
|
2550
2417
|
</div>
|
|
2551
2418
|
<div class="vital-signs" id="vitalSigns">
|
|
@@ -2588,7 +2455,7 @@
|
|
|
2588
2455
|
<div class="session-list" id="sessionList">
|
|
2589
2456
|
<div class="empty-state" id="emptyState">
|
|
2590
2457
|
<div class="icon">💭</div>
|
|
2591
|
-
<p>No running
|
|
2458
|
+
<p>No sessions running<br>Create one to start working with your agent</p>
|
|
2592
2459
|
</div>
|
|
2593
2460
|
</div>
|
|
2594
2461
|
</aside>
|
|
@@ -2700,12 +2567,12 @@
|
|
|
2700
2567
|
</div>
|
|
2701
2568
|
</div>
|
|
2702
2569
|
|
|
2703
|
-
<!-- Drop Zone
|
|
2570
|
+
<!-- Send Content tab (was Drop Zone) -->
|
|
2704
2571
|
<div class="dropzone-container" id="dropzoneTab" style="display:none">
|
|
2705
2572
|
<div class="dropzone-panel">
|
|
2706
2573
|
<div class="dropzone-header">
|
|
2707
|
-
<h2>
|
|
2708
|
-
<p class="dropzone-subtitle">
|
|
2574
|
+
<h2>Send Content</h2>
|
|
2575
|
+
<p class="dropzone-subtitle">Paste text content to send directly to a running session</p>
|
|
2709
2576
|
</div>
|
|
2710
2577
|
|
|
2711
2578
|
<div class="dropzone-form">
|
|
@@ -2751,8 +2618,11 @@
|
|
|
2751
2618
|
<option value="lastRun">Sort: Last Run</option>
|
|
2752
2619
|
</select>
|
|
2753
2620
|
</div>
|
|
2621
|
+
<div style="font-size:11px;color:var(--text-dim);padding:0 12px 8px;line-height:1.4">Monitor and control your scheduled tasks</div>
|
|
2622
|
+
<div id="jobsSummary" class="jobs-summary"></div>
|
|
2754
2623
|
<div class="jobs-filter-bar" id="jobsFilterBar">
|
|
2755
2624
|
<button class="jobs-filter-chip active" data-filter="all" onclick="setJobFilter('all')">All</button>
|
|
2625
|
+
<button class="jobs-filter-chip" data-filter="running" onclick="setJobFilter('running')">Running</button>
|
|
2756
2626
|
<button class="jobs-filter-chip" data-filter="failing" onclick="setJobFilter('failing')">Failing</button>
|
|
2757
2627
|
<button class="jobs-filter-chip" data-filter="disabled" onclick="setJobFilter('disabled')">Disabled</button>
|
|
2758
2628
|
</div>
|
|
@@ -2764,22 +2634,23 @@
|
|
|
2764
2634
|
<div class="jobs-detail-empty" id="jobsDetailEmpty">
|
|
2765
2635
|
<div style="text-align:center">
|
|
2766
2636
|
<div style="font-size:24px;margin-bottom:8px">⚙</div>
|
|
2767
|
-
<p>Select a job to view
|
|
2637
|
+
<p>Select a job to view its schedule, history, and configuration</p>
|
|
2768
2638
|
</div>
|
|
2769
2639
|
</div>
|
|
2770
2640
|
<div class="jobs-detail-content" id="jobsDetailContent" style="display:none"></div>
|
|
2771
2641
|
</div>
|
|
2772
2642
|
</div>
|
|
2773
2643
|
|
|
2774
|
-
<!--
|
|
2644
|
+
<!-- Health Tab (was Systems) -->
|
|
2775
2645
|
<div class="systems-container" id="systemsTab" style="display:none">
|
|
2776
2646
|
<div class="systems-main">
|
|
2777
2647
|
<!-- Overview (card grid) -->
|
|
2778
2648
|
<div id="systemsOverview">
|
|
2779
2649
|
<div class="systems-header">
|
|
2780
|
-
<h2>
|
|
2650
|
+
<h2>Health</h2>
|
|
2781
2651
|
<button class="systems-refresh" onclick="loadSystems()">Refresh</button>
|
|
2782
2652
|
</div>
|
|
2653
|
+
<div style="font-size:12px;color:var(--text-dim);margin-bottom:16px;line-height:1.4">Your agent's health and running processes</div>
|
|
2783
2654
|
<div id="systemsBanner">
|
|
2784
2655
|
<div style="padding:20px;color:var(--text-dim);text-align:center">Loading...</div>
|
|
2785
2656
|
</div>
|
|
@@ -2789,54 +2660,57 @@
|
|
|
2789
2660
|
</div>
|
|
2790
2661
|
<!-- Detail view (single capability) -->
|
|
2791
2662
|
<div class="cap-detail-view" id="systemsDetailView">
|
|
2792
|
-
<button class="cap-detail-back" onclick="showSystemsOverview()">◀ Back to
|
|
2663
|
+
<button class="cap-detail-back" onclick="showSystemsOverview()">◀ Back to Health</button>
|
|
2793
2664
|
<div id="systemsDetailContent"></div>
|
|
2794
2665
|
</div>
|
|
2795
2666
|
</div>
|
|
2796
2667
|
</div>
|
|
2797
2668
|
|
|
2798
|
-
<!--
|
|
2799
|
-
<div class="
|
|
2800
|
-
<div class="
|
|
2801
|
-
<div class="
|
|
2802
|
-
<h2>
|
|
2803
|
-
<button class="
|
|
2669
|
+
<!-- Features Tab (was Discovery) -->
|
|
2670
|
+
<div class="features-container" id="featuresTab" style="display:none">
|
|
2671
|
+
<div class="features-main">
|
|
2672
|
+
<div class="features-header">
|
|
2673
|
+
<h2>Features</h2>
|
|
2674
|
+
<button class="features-refresh" onclick="loadFeatures()">Refresh</button>
|
|
2804
2675
|
</div>
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
<div class="
|
|
2810
|
-
|
|
2676
|
+
<div class="features-subtitle">Browse and configure your agent's capabilities. Features are opt-in and can be enabled or disabled at any time.</div>
|
|
2677
|
+
|
|
2678
|
+
<!-- Autonomy Profile -->
|
|
2679
|
+
<div class="autonomy-card" id="autonomyCard">
|
|
2680
|
+
<div class="autonomy-card-icon" id="autonomyIcon">🛡</div>
|
|
2681
|
+
<div class="autonomy-card-info">
|
|
2682
|
+
<div class="autonomy-card-label">Autonomy Profile</div>
|
|
2683
|
+
<div class="autonomy-card-level" id="autonomyLevel">Loading...</div>
|
|
2684
|
+
<div class="autonomy-card-desc" id="autonomyDesc"></div>
|
|
2811
2685
|
</div>
|
|
2686
|
+
<button class="autonomy-card-btn" onclick="showProfileSelector()">Change</button>
|
|
2812
2687
|
</div>
|
|
2813
2688
|
|
|
2814
|
-
<!-- Feature
|
|
2815
|
-
<div
|
|
2816
|
-
<
|
|
2817
|
-
<div class="discovery-filter-bar">
|
|
2818
|
-
<button class="discovery-filter active" data-filter="all" onclick="setDiscoveryFilter('all')">All</button>
|
|
2819
|
-
<button class="discovery-filter" data-filter="enabled" onclick="setDiscoveryFilter('enabled')">Enabled</button>
|
|
2820
|
-
<button class="discovery-filter" data-filter="undiscovered" onclick="setDiscoveryFilter('undiscovered')">Undiscovered</button>
|
|
2821
|
-
<button class="discovery-filter" data-filter="aware" onclick="setDiscoveryFilter('aware')">Aware</button>
|
|
2822
|
-
<button class="discovery-filter" data-filter="cooldown" onclick="setDiscoveryFilter('cooldown')">In Cooldown</button>
|
|
2823
|
-
</div>
|
|
2824
|
-
<div class="feature-grid" id="featureGrid"></div>
|
|
2689
|
+
<!-- Feature Categories (populated by JS) -->
|
|
2690
|
+
<div id="featureCatalog">
|
|
2691
|
+
<div style="padding:20px;color:var(--text-dim);text-align:center">Loading features...</div>
|
|
2825
2692
|
</div>
|
|
2693
|
+
</div>
|
|
2826
2694
|
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2695
|
+
<!-- Feature Detail View (hidden by default) -->
|
|
2696
|
+
<div class="features-main feat-detail-view" id="featDetailView">
|
|
2697
|
+
<button class="feat-detail-back" onclick="showFeaturesOverview()">◀ Back to Features</button>
|
|
2698
|
+
<div id="featDetailContent"></div>
|
|
2699
|
+
</div>
|
|
2832
2700
|
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2701
|
+
<!-- Profile Selector (hidden by default) -->
|
|
2702
|
+
<div class="features-main feat-detail-view" id="profileSelectorView">
|
|
2703
|
+
<button class="feat-detail-back" onclick="showFeaturesOverview()">◀ Back to Features</button>
|
|
2704
|
+
<div class="feat-detail-header">
|
|
2705
|
+
<div class="feat-detail-title"><h3>Choose Autonomy Profile</h3></div>
|
|
2706
|
+
<div class="feat-detail-description">Your autonomy profile controls how independently your agent operates. Higher autonomy means fewer interruptions, but less oversight.</div>
|
|
2839
2707
|
</div>
|
|
2708
|
+
<div class="profile-selector" id="profileSelector"></div>
|
|
2709
|
+
<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:16px">
|
|
2710
|
+
<button class="autonomy-card-btn" onclick="showFeaturesOverview()">Cancel</button>
|
|
2711
|
+
<button class="autonomy-card-btn" id="profileConfirmBtn" style="border-color:var(--accent);color:var(--accent)" onclick="confirmProfileChange()">Apply Profile</button>
|
|
2712
|
+
</div>
|
|
2713
|
+
<div id="profileHistory" style="margin-top:24px"></div>
|
|
2840
2714
|
</div>
|
|
2841
2715
|
</div>
|
|
2842
2716
|
|
|
@@ -3131,20 +3005,20 @@
|
|
|
3131
3005
|
if (tele.toolsUsed && tele.toolsUsed.length > 0) {
|
|
3132
3006
|
const toolList = tele.toolsUsed.slice(0, 4).join(', ');
|
|
3133
3007
|
const extra = tele.toolsUsed.length > 4 ? ` +${tele.toolsUsed.length - 4}` : '';
|
|
3134
|
-
parts.push(`<span class="telemetry-tag">${escapeHtml(toolList)}${extra}</span>`);
|
|
3008
|
+
parts.push(`<span class="telemetry-tag" title="Tools used in this session">${escapeHtml(toolList)}${extra}</span>`);
|
|
3135
3009
|
}
|
|
3136
3010
|
if (tele.subagentsSpawned && tele.subagentsSpawned.length > 0) {
|
|
3137
|
-
parts.push(`<span class="telemetry-tag active-subagents">${tele.subagentsSpawned.length}
|
|
3011
|
+
parts.push(`<span class="telemetry-tag active-subagents" title="Background tasks spawned by this session">${tele.subagentsSpawned.length} background task${tele.subagentsSpawned.length > 1 ? 's' : ''}</span>`);
|
|
3138
3012
|
}
|
|
3139
3013
|
if (tele.lastActivity) {
|
|
3140
3014
|
const lastMs = Date.now() - new Date(tele.lastActivity).getTime();
|
|
3141
3015
|
const lastMins = Math.floor(lastMs / 60000);
|
|
3142
3016
|
if (lastMins > 10) {
|
|
3143
|
-
parts.push(`<span class="telemetry-tag stale">
|
|
3017
|
+
parts.push(`<span class="telemetry-tag stale" title="No activity for ${lastMins} minutes">waiting ${lastMins}m</span>`);
|
|
3144
3018
|
}
|
|
3145
3019
|
}
|
|
3146
3020
|
if (tele.eventCount) {
|
|
3147
|
-
parts.push(`<span class="telemetry-tag">${tele.eventCount} events</span>`);
|
|
3021
|
+
parts.push(`<span class="telemetry-tag" title="Total events in this session">${tele.eventCount} events</span>`);
|
|
3148
3022
|
}
|
|
3149
3023
|
if (parts.length > 0) {
|
|
3150
3024
|
telemetryHtml = `<div class="session-telemetry">${parts.join('')}</div>`;
|
|
@@ -3773,10 +3647,10 @@
|
|
|
3773
3647
|
onDeactivate: () => { disconnectJobsSSE(); },
|
|
3774
3648
|
},
|
|
3775
3649
|
{
|
|
3776
|
-
id: '
|
|
3777
|
-
panels: ['
|
|
3650
|
+
id: 'features',
|
|
3651
|
+
panels: ['featuresTab'],
|
|
3778
3652
|
display: ['flex'],
|
|
3779
|
-
onActivate: () => { if (!
|
|
3653
|
+
onActivate: () => { if (!featuresLoaded) loadFeatures(); },
|
|
3780
3654
|
},
|
|
3781
3655
|
{
|
|
3782
3656
|
id: 'systems',
|
|
@@ -4638,7 +4512,7 @@
|
|
|
4638
4512
|
<span class="dz-paste-meta">${chars} chars · ${age}</span>
|
|
4639
4513
|
</div>
|
|
4640
4514
|
<div style="display:flex;align-items:center;gap:8px">
|
|
4641
|
-
<span class="dz-paste-status ${p.status}">${p.status}</span>
|
|
4515
|
+
<span class="dz-paste-status ${p.status}">${p.status === 'notified' ? 'delivered' : p.status === 'queued' || p.status === 'written' ? 'waiting' : p.status}</span>
|
|
4642
4516
|
<button class="dz-paste-delete" onclick="deletePaste('${esc(p.pasteId)}')" title="Delete">×</button>
|
|
4643
4517
|
</div>
|
|
4644
4518
|
</div>`;
|
|
@@ -4674,10 +4548,10 @@
|
|
|
4674
4548
|
const chars = data.contentLength?.toLocaleString() || content.length.toLocaleString();
|
|
4675
4549
|
if (data.status === 'notified') {
|
|
4676
4550
|
status.className = 'dropzone-status success';
|
|
4677
|
-
status.textContent = `
|
|
4551
|
+
status.textContent = `Delivered ${chars} chars to session "${data.sessionName}"`;
|
|
4678
4552
|
} else {
|
|
4679
4553
|
status.className = 'dropzone-status queued';
|
|
4680
|
-
status.textContent = `
|
|
4554
|
+
status.textContent = `Waiting for session \u2014 ${chars} chars will be delivered when a session starts.`;
|
|
4681
4555
|
}
|
|
4682
4556
|
status.style.display = 'block';
|
|
4683
4557
|
|
|
@@ -4763,8 +4637,8 @@
|
|
|
4763
4637
|
const sessVital = document.getElementById('vitalSessions');
|
|
4764
4638
|
const cur = h.sessions?.current ?? 0;
|
|
4765
4639
|
const max = h.sessions?.max ?? 0;
|
|
4766
|
-
sessEl.textContent = cur + '/' + max;
|
|
4767
|
-
if (cur >= max) {
|
|
4640
|
+
sessEl.textContent = cur + (max > 0 ? '/' + max : '') + ' session' + (cur !== 1 ? 's' : '');
|
|
4641
|
+
if (max > 0 && cur >= max) {
|
|
4768
4642
|
sessVital.className = 'vital warn';
|
|
4769
4643
|
} else {
|
|
4770
4644
|
sessVital.className = 'vital';
|
|
@@ -4874,6 +4748,7 @@
|
|
|
4874
4748
|
const data = await apiFetch('/jobs');
|
|
4875
4749
|
const jobs = data.jobs || data;
|
|
4876
4750
|
jobsData = Array.isArray(jobs) ? jobs : [];
|
|
4751
|
+
renderJobsSummary();
|
|
4877
4752
|
renderJobList();
|
|
4878
4753
|
} catch (e) {
|
|
4879
4754
|
document.getElementById('jobsList').innerHTML =
|
|
@@ -4881,6 +4756,19 @@
|
|
|
4881
4756
|
}
|
|
4882
4757
|
}
|
|
4883
4758
|
|
|
4759
|
+
function renderJobsSummary() {
|
|
4760
|
+
const total = jobsData.length;
|
|
4761
|
+
const running = jobsData.filter(j => j.state && j.state.lastResult === 'pending').length;
|
|
4762
|
+
const failing = jobsData.filter(j => j.enabled && (j.state?.consecutiveFailures || 0) > 0).length;
|
|
4763
|
+
const disabled = jobsData.filter(j => !j.enabled).length;
|
|
4764
|
+
const healthy = total - failing - disabled;
|
|
4765
|
+
document.getElementById('jobsSummary').innerHTML = `
|
|
4766
|
+
<div class="jobs-summary-card"><div class="jobs-summary-val">${total}</div><div class="jobs-summary-label">Total</div></div>
|
|
4767
|
+
<div class="jobs-summary-card${running > 0 ? ' running' : ''}"><div class="jobs-summary-val">${running}</div><div class="jobs-summary-label">Running</div></div>
|
|
4768
|
+
<div class="jobs-summary-card${failing > 0 ? ' failing' : ''}"><div class="jobs-summary-val">${failing}</div><div class="jobs-summary-label">Failing</div></div>
|
|
4769
|
+
<div class="jobs-summary-card"><div class="jobs-summary-val">${disabled}</div><div class="jobs-summary-label">Disabled</div></div>`;
|
|
4770
|
+
}
|
|
4771
|
+
|
|
4884
4772
|
function setJobFilter(filter) {
|
|
4885
4773
|
jobFilter = filter;
|
|
4886
4774
|
document.querySelectorAll('.jobs-filter-chip').forEach(c => {
|
|
@@ -4894,6 +4782,9 @@
|
|
|
4894
4782
|
const sort = document.getElementById('jobsSort').value;
|
|
4895
4783
|
|
|
4896
4784
|
let filtered = jobsData.filter(j => {
|
|
4785
|
+
if (jobFilter === 'running') {
|
|
4786
|
+
return j.state && j.state.lastResult === 'pending';
|
|
4787
|
+
}
|
|
4897
4788
|
if (jobFilter === 'failing') {
|
|
4898
4789
|
const s = j.state || {};
|
|
4899
4790
|
return (s.consecutiveFailures || 0) > 0;
|
|
@@ -4943,6 +4834,8 @@
|
|
|
4943
4834
|
const lastResult = s.lastResult || '—';
|
|
4944
4835
|
const lastTime = s.lastRun ? timeAgo(new Date(s.lastRun)) : 'never';
|
|
4945
4836
|
const isActive = selectedJob === j.slug ? ' active' : '';
|
|
4837
|
+
const displayName = j.name || j.slug;
|
|
4838
|
+
const desc = j.description ? '<div style="font-size:10px;color:var(--text-dim);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + esc(j.description) + '</div>' : '';
|
|
4946
4839
|
|
|
4947
4840
|
const priorityBadge = (j.priority === 'critical' || j.priority === 'high')
|
|
4948
4841
|
? '<span class="priority-badge ' + esc(j.priority) + '">' + esc(j.priority) + '</span>' : '';
|
|
@@ -4950,9 +4843,10 @@
|
|
|
4950
4843
|
return '<div class="job-item' + isActive + '" onclick="selectJob(\'' + esc(j.slug) + '\')">'
|
|
4951
4844
|
+ '<div class="job-item-top">'
|
|
4952
4845
|
+ '<span class="job-status-dot ' + dotClass + '"></span>'
|
|
4953
|
-
+ '<span class="job-item-name">' + esc(
|
|
4846
|
+
+ '<span class="job-item-name">' + esc(displayName) + '</span>'
|
|
4954
4847
|
+ (failures > 0 ? '<span class="job-failure-count">' + failures + ' fail</span>' : '')
|
|
4955
4848
|
+ '</div>'
|
|
4849
|
+
+ desc
|
|
4956
4850
|
+ '<div class="job-item-meta">'
|
|
4957
4851
|
+ '<span>' + esc(schedule) + '</span>'
|
|
4958
4852
|
+ '<span class="model-badge">' + esc(j.model || '—') + '</span>'
|
|
@@ -4991,23 +4885,46 @@
|
|
|
4991
4885
|
let statusText = 'Healthy';
|
|
4992
4886
|
let statusClass = 'success';
|
|
4993
4887
|
if (!job.enabled) { statusText = 'Disabled'; statusClass = ''; }
|
|
4994
|
-
else if (failures >= 3) { statusText = '
|
|
4995
|
-
else if (failures > 0) { statusText = '
|
|
4996
|
-
else if (isRunning) { statusText = 'Running'; statusClass = ''; }
|
|
4997
|
-
|
|
4998
|
-
const
|
|
4888
|
+
else if (failures >= 3) { statusText = 'This job has failed ' + failures + ' times in a row'; statusClass = 'error'; }
|
|
4889
|
+
else if (failures > 0) { statusText = failures + ' recent failure' + (failures > 1 ? 's' : ''); statusClass = 'error'; }
|
|
4890
|
+
else if (isRunning) { statusText = 'Running now'; statusClass = ''; }
|
|
4891
|
+
|
|
4892
|
+
const displayName = job.name || job.slug;
|
|
4893
|
+
const nextTimeAbs = s.nextScheduled ? new Date(s.nextScheduled).toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : '—';
|
|
4894
|
+
const lastTimeAbs = s.lastRun ? new Date(s.lastRun).toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : 'Never';
|
|
4895
|
+
|
|
4896
|
+
// Supervision explanation
|
|
4897
|
+
const supervisionLabels = { tier0: 'Basic (no AI verification)', tier1: 'AI-supervised (Haiku validates)', tier2: 'Full AI (Sonnet/Opus handles reasoning)' };
|
|
4898
|
+
const execTypeLabels = { skill: 'Runs a skill', prompt: 'Runs a prompt', script: 'Runs a script' };
|
|
4899
|
+
|
|
4900
|
+
// Build config section
|
|
4901
|
+
let configHtml = '<div class="job-config-section">'
|
|
4902
|
+
+ '<div class="job-config-title" onclick="this.nextElementSibling.style.display=this.nextElementSibling.style.display===\'none\'?\'grid\':\'none\'">▶ Configuration</div>'
|
|
4903
|
+
+ '<div class="job-config-grid" style="display:none">'
|
|
4904
|
+
+ '<div class="job-config-key">Schedule</div><div class="job-config-val">' + esc(schedule) + ' (' + esc(job.schedule) + ')</div>'
|
|
4905
|
+
+ '<div class="job-config-key">Model</div><div class="job-config-val">' + esc(job.model || 'default') + '</div>'
|
|
4906
|
+
+ '<div class="job-config-key">Priority</div><div class="job-config-val">' + esc(job.priority || 'medium') + '</div>'
|
|
4907
|
+
+ '<div class="job-config-key">Execution</div><div class="job-config-val">' + esc(execTypeLabels[job.execute?.type] || job.execute?.type || '—') + '</div>';
|
|
4908
|
+
if (job.supervision) {
|
|
4909
|
+
configHtml += '<div class="job-config-key">Supervision</div><div class="job-config-val">' + esc(supervisionLabels[job.supervision] || job.supervision) + '</div>';
|
|
4910
|
+
}
|
|
4911
|
+
if (job.tags && job.tags.length > 0) {
|
|
4912
|
+
configHtml += '<div class="job-config-key">Tags</div><div class="job-config-val">' + job.tags.map(t => esc(t)).join(', ') + '</div>';
|
|
4913
|
+
}
|
|
4914
|
+
if (job.gate) {
|
|
4915
|
+
configHtml += '<div class="job-config-key">Pre-flight check</div><div class="job-config-val">Yes (runs before each execution)</div>';
|
|
4916
|
+
}
|
|
4917
|
+
if (job.machines && job.machines.length > 0) {
|
|
4918
|
+
configHtml += '<div class="job-config-key">Machines</div><div class="job-config-val">' + job.machines.map(m => esc(m)).join(', ') + '</div>';
|
|
4919
|
+
}
|
|
4920
|
+
configHtml += '</div></div>';
|
|
4999
4921
|
|
|
5000
4922
|
detail.innerHTML = '<div class="job-detail-header">'
|
|
5001
4923
|
+ '<div>'
|
|
5002
4924
|
+ '<button class="back-btn" onclick="jobsGoBack()" style="display:none;margin-right:8px">←</button>'
|
|
5003
|
-
+ '<h3>' + esc(
|
|
5004
|
-
+ '<div class="job-desc">' + esc(job.description ||
|
|
5005
|
-
+ '
|
|
5006
|
-
+ '<span>' + esc(job.schedule) + ' (' + esc(schedule) + ')</span>'
|
|
5007
|
-
+ '<span class="model-badge">' + esc(job.model || '—') + '</span>'
|
|
5008
|
-
+ (job.priority ? '<span class="priority-badge ' + esc(job.priority) + '">' + esc(job.priority) + '</span>' : '')
|
|
5009
|
-
+ '<span>Tags: ' + tags + '</span>'
|
|
5010
|
-
+ '</div></div>'
|
|
4925
|
+
+ '<h3>' + esc(displayName) + '</h3>'
|
|
4926
|
+
+ '<div class="job-desc">' + esc(job.description || '') + '</div>'
|
|
4927
|
+
+ '</div>'
|
|
5011
4928
|
+ '<div class="job-detail-actions">'
|
|
5012
4929
|
+ '<button class="job-run-btn" id="jobRunBtn" onclick="runJob(\'' + esc(job.slug) + '\')"'
|
|
5013
4930
|
+ (isRunning ? ' disabled' : '') + '>'
|
|
@@ -5018,14 +4935,16 @@
|
|
|
5018
4935
|
+ '</div></div>'
|
|
5019
4936
|
+ '<div class="job-state-card">'
|
|
5020
4937
|
+ '<div class="state-row"><span>Status</span><span class="state-val ' + statusClass + '">' + esc(statusText) + '</span></div>'
|
|
5021
|
-
+ '<div class="state-row"><span>Last Run</span><span class="state-val">' + esc(lastTime) + '</span></div>'
|
|
4938
|
+
+ '<div class="state-row"><span>Last Run</span><span class="state-val">' + esc(lastTimeAbs) + ' (' + esc(lastTime) + ')</span></div>'
|
|
5022
4939
|
+ '<div class="state-row"><span>Last Result</span><span class="state-val ' + (lastResult === 'success' ? 'success' : failures > 0 ? 'error' : '') + '">' + esc(lastResult) + '</span></div>'
|
|
5023
|
-
+ (s.lastError ? '<div class="state-row"><span>Error</span><span class="state-val error">' + esc(s.lastError) + '</span></div>' : '')
|
|
5024
|
-
+ '<div class="state-row"><span>Next Run</span><span class="state-val">' + esc(
|
|
4940
|
+
+ (s.lastError ? '<div class="state-row"><span>Error</span><span class="state-val error" style="word-break:break-word">' + esc(s.lastError) + '</span></div>' : '')
|
|
4941
|
+
+ '<div class="state-row"><span>Next Run</span><span class="state-val">' + esc(nextTimeAbs) + '</span></div>'
|
|
5025
4942
|
+ '</div>'
|
|
5026
4943
|
+ '<div id="jobSparkline" class="job-sparkline"></div>'
|
|
4944
|
+
+ '<div class="sparkline-legend"><div class="sparkline-legend-item"><div class="sparkline-legend-dot success"></div>Success</div><div class="sparkline-legend-item"><div class="sparkline-legend-dot failure"></div>Failure</div><div class="sparkline-legend-item"><div class="sparkline-legend-dot skipped"></div>Skipped</div></div>'
|
|
5027
4945
|
+ '<div class="job-history-section"><h4>Run History</h4>'
|
|
5028
|
-
+ '<div id="jobHistoryTable">Loading...</div></div>'
|
|
4946
|
+
+ '<div id="jobHistoryTable">Loading...</div></div>'
|
|
4947
|
+
+ configHtml;
|
|
5029
4948
|
|
|
5030
4949
|
// Show back button on mobile
|
|
5031
4950
|
if (window.innerWidth <= 768) {
|
|
@@ -5072,12 +4991,12 @@
|
|
|
5072
4991
|
const result = r.result || '—';
|
|
5073
4992
|
const dur = r.durationSeconds != null ? r.durationSeconds + 's'
|
|
5074
4993
|
: r.durationMs != null ? Math.round(r.durationMs / 1000) + 's' : '—';
|
|
5075
|
-
const error = r.error ? esc(r.error)
|
|
4994
|
+
const error = r.error ? esc(r.error) : '—';
|
|
5076
4995
|
return '<tr>'
|
|
5077
4996
|
+ '<td>' + esc(time) + '</td>'
|
|
5078
4997
|
+ '<td><span class="result-badge ' + esc(result) + '">' + esc(result) + '</span></td>'
|
|
5079
4998
|
+ '<td>' + esc(dur) + '</td>'
|
|
5080
|
-
+ '<td style="color:var(--text-dim);max-width:
|
|
4999
|
+
+ '<td style="color:var(--text-dim);max-width:300px;word-break:break-word" title="' + esc(r.error || '') + '">' + error + '</td>'
|
|
5081
5000
|
+ '</tr>';
|
|
5082
5001
|
}).join('') + '</tbody></table>';
|
|
5083
5002
|
} catch (e) {
|
|
@@ -5240,198 +5159,289 @@
|
|
|
5240
5159
|
}
|
|
5241
5160
|
}
|
|
5242
5161
|
|
|
5243
|
-
// ──
|
|
5244
|
-
let
|
|
5245
|
-
let
|
|
5246
|
-
let
|
|
5162
|
+
// ── Features Tab (was Discovery) ──────────────────────────
|
|
5163
|
+
let featuresLoaded = false;
|
|
5164
|
+
let featuresData = null; // { features: [], autonomy: {} }
|
|
5165
|
+
let selectedProfile = null;
|
|
5247
5166
|
|
|
5248
|
-
const
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
declined: 'var(--red)',
|
|
5254
|
-
enabled: 'var(--accent)',
|
|
5255
|
-
disabled: '#555',
|
|
5167
|
+
const AUTONOMY_PROFILES = {
|
|
5168
|
+
cautious: { icon: '🛡', desc: 'You approve everything. Maximum oversight, minimum agent independence.' },
|
|
5169
|
+
supervised: { icon: '👁', desc: 'Routine tasks run automatically. You approve important decisions. Recommended for most users.' },
|
|
5170
|
+
collaborative: { icon: '🤝', desc: 'Agent handles most decisions independently. Keeps you informed of what it does.' },
|
|
5171
|
+
autonomous: { icon: '🚀', desc: 'Full self-governance. Agent handles everything and reports results.' },
|
|
5256
5172
|
};
|
|
5257
5173
|
|
|
5258
|
-
const
|
|
5174
|
+
const CATEGORY_LABELS = {
|
|
5175
|
+
communication: 'Communication',
|
|
5176
|
+
infrastructure: 'Infrastructure',
|
|
5177
|
+
intelligence: 'Intelligence',
|
|
5178
|
+
safety: 'Safety & Trust',
|
|
5179
|
+
};
|
|
5180
|
+
const CATEGORY_ORDER = ['safety', 'communication', 'infrastructure', 'intelligence'];
|
|
5259
5181
|
|
|
5260
|
-
async function
|
|
5261
|
-
|
|
5182
|
+
async function loadFeatures() {
|
|
5183
|
+
featuresLoaded = true;
|
|
5262
5184
|
try {
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5185
|
+
const [rawFeatures, autonomy] = await Promise.all([
|
|
5186
|
+
apiFetch('/features'),
|
|
5187
|
+
apiFetch('/autonomy').catch(() => ({ profile: 'supervised' })),
|
|
5188
|
+
]);
|
|
5189
|
+
// API returns { features: [{ definition: {...}, state: {...} }] }
|
|
5190
|
+
const featureList = rawFeatures?.features || rawFeatures || [];
|
|
5191
|
+
const flattened = featureList.map(f => {
|
|
5192
|
+
if (f.definition) {
|
|
5193
|
+
return { ...f.definition, ...f.state, discoveryState: f.state?.discoveryState, enabled: f.state?.enabled };
|
|
5194
|
+
}
|
|
5195
|
+
return f;
|
|
5196
|
+
});
|
|
5197
|
+
featuresData = { features: flattened, autonomy: autonomy || {} };
|
|
5198
|
+
renderAutonomyCard();
|
|
5199
|
+
renderFeatureCatalog();
|
|
5269
5200
|
} catch (e) {
|
|
5270
|
-
document.getElementById('
|
|
5271
|
-
'<div style="padding:20px;color:var(--red);text-align:center">Failed to load
|
|
5201
|
+
document.getElementById('featureCatalog').innerHTML =
|
|
5202
|
+
'<div style="padding:20px;color:var(--red);text-align:center">Failed to load features</div>';
|
|
5272
5203
|
}
|
|
5273
5204
|
}
|
|
5274
5205
|
|
|
5275
|
-
function
|
|
5276
|
-
if (!
|
|
5277
|
-
const
|
|
5278
|
-
const
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
<div class="metric-label">Total Features</div>
|
|
5283
|
-
</div>
|
|
5284
|
-
<div class="metric-card">
|
|
5285
|
-
<div class="metric-value">${d.enabledCount}</div>
|
|
5286
|
-
<div class="metric-label">Enabled</div>
|
|
5287
|
-
</div>
|
|
5288
|
-
<div class="metric-card">
|
|
5289
|
-
<div class="metric-value">${Math.round(d.discoveryRate * 100)}%</div>
|
|
5290
|
-
<div class="metric-label">Discovery Rate</div>
|
|
5291
|
-
</div>
|
|
5292
|
-
<div class="metric-card">
|
|
5293
|
-
<div class="metric-value">${d.cooldowns.filter(c => c.cooldownExpiresAt).length}</div>
|
|
5294
|
-
<div class="metric-label">In Cooldown</div>
|
|
5295
|
-
</div>
|
|
5296
|
-
</div>`;
|
|
5297
|
-
// Insert before funnel
|
|
5298
|
-
const section = document.querySelector('.discovery-section');
|
|
5299
|
-
if (section && !document.getElementById('discoveryMetrics')) {
|
|
5300
|
-
const div = document.createElement('div');
|
|
5301
|
-
div.id = 'discoveryMetrics';
|
|
5302
|
-
div.innerHTML = metricsHtml;
|
|
5303
|
-
section.parentNode.insertBefore(div, section);
|
|
5304
|
-
}
|
|
5305
|
-
}
|
|
5306
|
-
|
|
5307
|
-
function renderFunnel() {
|
|
5308
|
-
if (!discoveryData) return;
|
|
5309
|
-
const funnel = discoveryData.funnel;
|
|
5310
|
-
const maxVal = Math.max(1, ...Object.values(funnel));
|
|
5311
|
-
const chart = document.getElementById('funnelChart');
|
|
5312
|
-
|
|
5313
|
-
chart.innerHTML = FUNNEL_ORDER.map(state => {
|
|
5314
|
-
const count = funnel[state] || 0;
|
|
5315
|
-
const height = Math.max(2, (count / maxVal) * 80);
|
|
5316
|
-
const color = FUNNEL_COLORS[state] || '#444';
|
|
5317
|
-
return `<div class="funnel-bar">
|
|
5318
|
-
<div class="funnel-bar-count">${count}</div>
|
|
5319
|
-
<div class="funnel-bar-fill" style="height:${height}px;background:${color}"></div>
|
|
5320
|
-
<div class="funnel-bar-label">${state}</div>
|
|
5321
|
-
</div>`;
|
|
5322
|
-
}).join('');
|
|
5323
|
-
}
|
|
5324
|
-
|
|
5325
|
-
function setDiscoveryFilter(filter) {
|
|
5326
|
-
discoveryFilter = filter;
|
|
5327
|
-
document.querySelectorAll('.discovery-filter').forEach(c => {
|
|
5328
|
-
c.classList.toggle('active', c.dataset.filter === filter);
|
|
5329
|
-
});
|
|
5330
|
-
renderFeatureGrid();
|
|
5206
|
+
function renderAutonomyCard() {
|
|
5207
|
+
if (!featuresData) return;
|
|
5208
|
+
const profile = featuresData.autonomy.profile || featuresData.autonomy.autonomyProfile || 'supervised';
|
|
5209
|
+
const info = AUTONOMY_PROFILES[profile] || AUTONOMY_PROFILES.supervised;
|
|
5210
|
+
document.getElementById('autonomyIcon').innerHTML = info.icon;
|
|
5211
|
+
document.getElementById('autonomyLevel').textContent = profile;
|
|
5212
|
+
document.getElementById('autonomyDesc').textContent = info.desc;
|
|
5331
5213
|
}
|
|
5332
5214
|
|
|
5333
|
-
function
|
|
5334
|
-
if (!
|
|
5335
|
-
const
|
|
5336
|
-
const
|
|
5215
|
+
function renderFeatureCatalog() {
|
|
5216
|
+
if (!featuresData) return;
|
|
5217
|
+
const catalog = document.getElementById('featureCatalog');
|
|
5218
|
+
const features = featuresData.features;
|
|
5337
5219
|
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
filtered = cooldowns.filter(c => ['aware', 'interested', 'deferred'].includes(c.discoveryState));
|
|
5345
|
-
} else if (discoveryFilter === 'cooldown') {
|
|
5346
|
-
filtered = cooldowns.filter(c => c.cooldownExpiresAt || c.quieted);
|
|
5220
|
+
// Group by category
|
|
5221
|
+
const groups = {};
|
|
5222
|
+
for (const f of features) {
|
|
5223
|
+
const cat = f.category || 'other';
|
|
5224
|
+
if (!groups[cat]) groups[cat] = [];
|
|
5225
|
+
groups[cat].push(f);
|
|
5347
5226
|
}
|
|
5348
5227
|
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5228
|
+
let html = '';
|
|
5229
|
+
for (const cat of CATEGORY_ORDER) {
|
|
5230
|
+
const items = groups[cat];
|
|
5231
|
+
if (!items || items.length === 0) continue;
|
|
5232
|
+
const label = CATEGORY_LABELS[cat] || cat;
|
|
5233
|
+
html += `<div class="feat-category">
|
|
5234
|
+
<div class="feat-category-title">${esc(label)}</div>
|
|
5235
|
+
<div class="feat-grid">`;
|
|
5236
|
+
for (const f of items) {
|
|
5237
|
+
const isEnabled = f.discoveryState === 'enabled' || f.enabled;
|
|
5238
|
+
const oneLiner = f.oneLiner || f.description || '';
|
|
5239
|
+
html += `<div class="feat-card" onclick="showFeatureDetail('${esc(f.id)}')">
|
|
5240
|
+
<div class="feat-card-top">
|
|
5241
|
+
<span class="feat-card-name">${esc(f.name)}</span>
|
|
5242
|
+
<label class="feat-toggle" onclick="event.stopPropagation()">
|
|
5243
|
+
<input type="checkbox" ${isEnabled ? 'checked' : ''} onchange="toggleFeature('${esc(f.id)}', this.checked)">
|
|
5244
|
+
<span class="feat-toggle-slider"></span>
|
|
5245
|
+
</label>
|
|
5246
|
+
<span class="feat-card-arrow">▶</span>
|
|
5247
|
+
</div>
|
|
5248
|
+
<div class="feat-card-desc">${esc(oneLiner)}</div>
|
|
5249
|
+
</div>`;
|
|
5250
|
+
}
|
|
5251
|
+
html += '</div></div>';
|
|
5352
5252
|
}
|
|
5353
5253
|
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5254
|
+
// Handle uncategorized
|
|
5255
|
+
for (const cat of Object.keys(groups)) {
|
|
5256
|
+
if (CATEGORY_ORDER.includes(cat)) continue;
|
|
5257
|
+
const items = groups[cat];
|
|
5258
|
+
html += `<div class="feat-category">
|
|
5259
|
+
<div class="feat-category-title">${esc(cat)}</div>
|
|
5260
|
+
<div class="feat-grid">`;
|
|
5261
|
+
for (const f of items) {
|
|
5262
|
+
const isEnabled = f.discoveryState === 'enabled' || f.enabled;
|
|
5263
|
+
html += `<div class="feat-card" onclick="showFeatureDetail('${esc(f.id)}')">
|
|
5264
|
+
<div class="feat-card-top">
|
|
5265
|
+
<span class="feat-card-name">${esc(f.name)}</span>
|
|
5266
|
+
<label class="feat-toggle" onclick="event.stopPropagation()">
|
|
5267
|
+
<input type="checkbox" ${isEnabled ? 'checked' : ''} onchange="toggleFeature('${esc(f.id)}', this.checked)">
|
|
5268
|
+
<span class="feat-toggle-slider"></span>
|
|
5269
|
+
</label>
|
|
5270
|
+
<span class="feat-card-arrow">▶</span>
|
|
5271
|
+
</div>
|
|
5272
|
+
<div class="feat-card-desc">${esc(f.oneLiner || f.description || '')}</div>
|
|
5273
|
+
</div>`;
|
|
5274
|
+
}
|
|
5275
|
+
html += '</div></div>';
|
|
5276
|
+
}
|
|
5358
5277
|
|
|
5359
|
-
|
|
5360
|
-
<div class="feature-card-top">
|
|
5361
|
-
<span class="feature-card-name">${esc(c.featureName)}</span>
|
|
5362
|
-
<span class="feature-state-badge ${esc(c.discoveryState)}">${esc(c.discoveryState)}</span>
|
|
5363
|
-
</div>
|
|
5364
|
-
<div class="feature-card-meta">
|
|
5365
|
-
<span>surfaced ${c.surfaceCount}/${c.maxSurfaces}</span>
|
|
5366
|
-
${cooldownNote}
|
|
5367
|
-
</div>
|
|
5368
|
-
</div>`;
|
|
5369
|
-
}).join('');
|
|
5278
|
+
catalog.innerHTML = html || '<div style="padding:20px;color:var(--text-dim);text-align:center">No features found</div>';
|
|
5370
5279
|
}
|
|
5371
5280
|
|
|
5372
|
-
function
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5281
|
+
async function toggleFeature(featureId, enable) {
|
|
5282
|
+
try {
|
|
5283
|
+
const to = enable ? 'enabled' : 'disabled';
|
|
5284
|
+
await apiFetch(`/features/${featureId}/transition`, {
|
|
5285
|
+
method: 'POST',
|
|
5286
|
+
headers: { 'Content-Type': 'application/json' },
|
|
5287
|
+
body: JSON.stringify({ to }),
|
|
5288
|
+
});
|
|
5289
|
+
// Refresh features data
|
|
5290
|
+
featuresLoaded = false;
|
|
5291
|
+
await loadFeatures();
|
|
5292
|
+
} catch (e) {
|
|
5293
|
+
showToast('Failed to update feature: ' + (e.message || e), 'error');
|
|
5294
|
+
featuresLoaded = false;
|
|
5295
|
+
await loadFeatures(); // Reset toggle state
|
|
5382
5296
|
}
|
|
5297
|
+
}
|
|
5383
5298
|
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
if (
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5299
|
+
function showFeatureDetail(featureId) {
|
|
5300
|
+
if (!featuresData) return;
|
|
5301
|
+
const f = featuresData.features.find(feat => feat.id === featureId);
|
|
5302
|
+
if (!f) return;
|
|
5303
|
+
|
|
5304
|
+
// Hide overview, show detail
|
|
5305
|
+
document.getElementById('featureCatalog').parentNode.querySelector('.features-header').style.display = 'none';
|
|
5306
|
+
document.getElementById('featureCatalog').parentNode.querySelector('.features-subtitle').style.display = 'none';
|
|
5307
|
+
document.getElementById('autonomyCard').style.display = 'none';
|
|
5308
|
+
document.getElementById('featureCatalog').style.display = 'none';
|
|
5309
|
+
document.getElementById('featDetailView').classList.add('active');
|
|
5310
|
+
|
|
5311
|
+
const isEnabled = f.discoveryState === 'enabled' || f.enabled;
|
|
5312
|
+
const content = document.getElementById('featDetailContent');
|
|
5313
|
+
|
|
5314
|
+
let dataHtml = '';
|
|
5315
|
+
if (f.dataImplications && f.dataImplications.length > 0) {
|
|
5316
|
+
dataHtml = `<div class="feat-detail-section">
|
|
5317
|
+
<div class="feat-detail-section-title">Data & Privacy</div>
|
|
5318
|
+
${f.dataImplications.map(d => `
|
|
5319
|
+
<div class="feat-data-item">
|
|
5320
|
+
<div class="feat-data-icon">📋</div>
|
|
5321
|
+
<div class="feat-data-content">
|
|
5322
|
+
<div class="feat-data-label">${esc(d.dataType)}</div>
|
|
5323
|
+
<div class="feat-data-desc">${esc(d.description || '')}${d.retention ? ' Retention: ' + esc(d.retention) + '.' : ''}${d.destination ? ' Destination: ' + esc(d.destination) + '.' : ''}</div>
|
|
5324
|
+
</div>
|
|
5325
|
+
</div>`).join('')}
|
|
5326
|
+
</div>`;
|
|
5395
5327
|
}
|
|
5396
5328
|
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
</div>`
|
|
5404
|
-
).join('');
|
|
5329
|
+
let reversibilityHtml = '';
|
|
5330
|
+
if (f.reversibilityNote) {
|
|
5331
|
+
reversibilityHtml = `<div class="feat-detail-section">
|
|
5332
|
+
<div class="feat-detail-section-title">How to Undo</div>
|
|
5333
|
+
<div class="feat-reversibility">${esc(f.reversibilityNote)}</div>
|
|
5334
|
+
</div>`;
|
|
5405
5335
|
}
|
|
5406
5336
|
|
|
5407
|
-
content.innerHTML =
|
|
5337
|
+
content.innerHTML = `
|
|
5338
|
+
<div class="feat-detail-header">
|
|
5339
|
+
<div class="feat-detail-title">
|
|
5340
|
+
<h3>${esc(f.name)}</h3>
|
|
5341
|
+
<span class="feat-detail-category">${esc(CATEGORY_LABELS[f.category] || f.category || '')}</span>
|
|
5342
|
+
<label class="feat-toggle">
|
|
5343
|
+
<input type="checkbox" ${isEnabled ? 'checked' : ''} onchange="toggleFeature('${esc(f.id)}', this.checked)">
|
|
5344
|
+
<span class="feat-toggle-slider"></span>
|
|
5345
|
+
</label>
|
|
5346
|
+
</div>
|
|
5347
|
+
</div>
|
|
5348
|
+
<div class="feat-detail-description">${esc(f.fullDescription || f.oneLiner || f.description || '')}</div>
|
|
5349
|
+
${dataHtml}
|
|
5350
|
+
${reversibilityHtml}
|
|
5351
|
+
`;
|
|
5352
|
+
}
|
|
5353
|
+
|
|
5354
|
+
function showFeaturesOverview() {
|
|
5355
|
+
document.getElementById('featDetailView').classList.remove('active');
|
|
5356
|
+
document.getElementById('profileSelectorView').classList.remove('active');
|
|
5357
|
+
const container = document.getElementById('featureCatalog').parentNode;
|
|
5358
|
+
container.querySelector('.features-header').style.display = '';
|
|
5359
|
+
container.querySelector('.features-subtitle').style.display = '';
|
|
5360
|
+
document.getElementById('autonomyCard').style.display = '';
|
|
5361
|
+
document.getElementById('featureCatalog').style.display = '';
|
|
5362
|
+
}
|
|
5363
|
+
|
|
5364
|
+
function showProfileSelector() {
|
|
5365
|
+
// Hide overview, show selector
|
|
5366
|
+
document.getElementById('featureCatalog').parentNode.querySelector('.features-header').style.display = 'none';
|
|
5367
|
+
document.getElementById('featureCatalog').parentNode.querySelector('.features-subtitle').style.display = 'none';
|
|
5368
|
+
document.getElementById('autonomyCard').style.display = 'none';
|
|
5369
|
+
document.getElementById('featureCatalog').style.display = 'none';
|
|
5370
|
+
document.getElementById('profileSelectorView').classList.add('active');
|
|
5371
|
+
|
|
5372
|
+
const currentProfile = featuresData?.autonomy?.profile || featuresData?.autonomy?.autonomyProfile || 'supervised';
|
|
5373
|
+
selectedProfile = currentProfile;
|
|
5374
|
+
|
|
5375
|
+
const selector = document.getElementById('profileSelector');
|
|
5376
|
+
selector.innerHTML = Object.entries(AUTONOMY_PROFILES).map(([key, info]) => {
|
|
5377
|
+
const isSelected = key === currentProfile;
|
|
5378
|
+
const isCurrent = key === currentProfile;
|
|
5379
|
+
const recommended = key === 'supervised' ? '<div class="profile-option-badge recommended">Recommended</div>' : '';
|
|
5380
|
+
return `<div class="profile-option${isSelected ? ' selected' : ''}" onclick="selectProfile('${key}')">
|
|
5381
|
+
<div class="profile-option-name">${info.icon} ${key}</div>
|
|
5382
|
+
<div class="profile-option-desc">${info.desc}</div>
|
|
5383
|
+
${isCurrent ? '<div class="profile-option-badge" style="background:rgba(255,255,255,0.06);color:var(--text-dim)">Current</div>' : ''}
|
|
5384
|
+
${recommended}
|
|
5385
|
+
</div>`;
|
|
5386
|
+
}).join('');
|
|
5387
|
+
|
|
5388
|
+
// Load profile history
|
|
5389
|
+
loadProfileHistory();
|
|
5408
5390
|
}
|
|
5409
5391
|
|
|
5410
|
-
function
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5392
|
+
function selectProfile(profile) {
|
|
5393
|
+
selectedProfile = profile;
|
|
5394
|
+
document.querySelectorAll('.profile-option').forEach(el => {
|
|
5395
|
+
el.classList.toggle('selected', el.querySelector('.profile-option-name').textContent.toLowerCase().includes(profile));
|
|
5396
|
+
});
|
|
5397
|
+
}
|
|
5414
5398
|
|
|
5415
|
-
|
|
5416
|
-
|
|
5399
|
+
async function confirmProfileChange() {
|
|
5400
|
+
if (!selectedProfile) return;
|
|
5401
|
+
const currentProfile = featuresData?.autonomy?.profile || featuresData?.autonomy?.autonomyProfile || 'supervised';
|
|
5402
|
+
if (selectedProfile === currentProfile) {
|
|
5403
|
+
showFeaturesOverview();
|
|
5417
5404
|
return;
|
|
5418
5405
|
}
|
|
5419
5406
|
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
}
|
|
5407
|
+
try {
|
|
5408
|
+
await apiFetch('/autonomy/profile', {
|
|
5409
|
+
method: 'POST',
|
|
5410
|
+
headers: { 'Content-Type': 'application/json' },
|
|
5411
|
+
body: JSON.stringify({ profile: selectedProfile }),
|
|
5412
|
+
});
|
|
5413
|
+
// Refresh autonomy data
|
|
5414
|
+
const autonomy = await apiFetch('/autonomy').catch(() => ({ profile: selectedProfile }));
|
|
5415
|
+
featuresData.autonomy = autonomy;
|
|
5416
|
+
renderAutonomyCard();
|
|
5417
|
+
showToast('Autonomy profile updated to ' + selectedProfile, 'success');
|
|
5418
|
+
showFeaturesOverview();
|
|
5419
|
+
} catch (e) {
|
|
5420
|
+
showToast('Failed to change profile: ' + (e.message || e), 'error');
|
|
5421
|
+
}
|
|
5422
|
+
}
|
|
5423
|
+
|
|
5424
|
+
async function loadProfileHistory() {
|
|
5425
|
+
try {
|
|
5426
|
+
const history = await apiFetch('/autonomy/history');
|
|
5427
|
+
const container = document.getElementById('profileHistory');
|
|
5428
|
+
if (!history || history.length === 0) {
|
|
5429
|
+
container.innerHTML = '';
|
|
5430
|
+
return;
|
|
5431
|
+
}
|
|
5432
|
+
container.innerHTML = `
|
|
5433
|
+
<div class="feat-detail-section-title">Profile History</div>
|
|
5434
|
+
${history.slice(0, 10).map(h => {
|
|
5435
|
+
const time = h.timestamp ? new Date(h.timestamp).toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : '';
|
|
5436
|
+
return `<div style="display:flex;gap:10px;padding:6px 0;font-size:12px;border-bottom:1px solid var(--border)">
|
|
5437
|
+
<span style="color:var(--text-dim);min-width:100px">${esc(time)}</span>
|
|
5438
|
+
<span style="color:var(--text)">${esc(h.from || '?')} → ${esc(h.to || '?')}</span>
|
|
5439
|
+
${h.reason ? '<span style="color:var(--text-dim);flex:1">' + esc(h.reason) + '</span>' : ''}
|
|
5440
|
+
</div>`;
|
|
5441
|
+
}).join('')}`;
|
|
5442
|
+
} catch (e) {
|
|
5443
|
+
// History not available, that's fine
|
|
5444
|
+
}
|
|
5435
5445
|
}
|
|
5436
5446
|
|
|
5437
5447
|
// ── Systems Tab ──────────────────────────────────────────────
|
|
@@ -5501,7 +5511,7 @@
|
|
|
5501
5511
|
<div class="cap-card-metric">${esc(cap.metric)}</div>
|
|
5502
5512
|
</div>`;
|
|
5503
5513
|
}).join('');
|
|
5504
|
-
capsEl.innerHTML = `<div class="cap-section-title">Active
|
|
5514
|
+
capsEl.innerHTML = `<div class="cap-section-title">Active Subsystems</div><div class="cap-grid">${cards}</div>`;
|
|
5505
5515
|
} else {
|
|
5506
5516
|
capsEl.innerHTML = '';
|
|
5507
5517
|
}
|
|
@@ -5526,8 +5536,8 @@
|
|
|
5526
5536
|
function buildPreviewStats(stats) {
|
|
5527
5537
|
if (!stats || typeof stats !== 'object') return '';
|
|
5528
5538
|
const map = {
|
|
5529
|
-
recoveries: '
|
|
5530
|
-
coherencePassed: '
|
|
5539
|
+
recoveries: 'Recovered', interventions: 'Auto-fixed', activeCases: 'Active',
|
|
5540
|
+
coherencePassed: 'Checks OK', coherenceFailed: 'Issues', memoryPercent: 'Memory',
|
|
5531
5541
|
enabledJobs: 'Jobs', activeJobSessions: 'Running', queueLength: 'Queued',
|
|
5532
5542
|
weeklyUsage: 'Weekly', fiveHourRate: '5h Rate',
|
|
5533
5543
|
topicMappings: 'Topics', totalMessages: 'Messages',
|
|
@@ -5612,15 +5622,15 @@
|
|
|
5612
5622
|
function buildStatCards(stats, capId) {
|
|
5613
5623
|
if (!stats || typeof stats !== 'object') return '';
|
|
5614
5624
|
const map = {
|
|
5615
|
-
interventions: '
|
|
5616
|
-
llmOverrides: '
|
|
5625
|
+
interventions: 'Auto-fixes', recoveries: 'Recovered', sessionDeaths: 'Crashes',
|
|
5626
|
+
llmOverrides: 'AI Decisions', activeCases: 'Active Issues', totalTriages: 'Investigated',
|
|
5617
5627
|
cooldowns: 'Cooldowns',
|
|
5618
|
-
coherenceChecks: 'Checks', coherencePassed: 'Passed', coherenceFailed: '
|
|
5628
|
+
coherenceChecks: 'Consistency Checks', coherencePassed: 'Passed', coherenceFailed: 'Issues Found', coherenceCorrected: 'Auto-corrected',
|
|
5619
5629
|
memoryPercent: 'Memory %', memoryFreeGB: 'Free GB', memoryTotalGB: 'Total GB',
|
|
5620
|
-
trackedProcesses: '
|
|
5630
|
+
trackedProcesses: 'Processes', orphanProcesses: 'Cleanup Needed', totalMemoryMB: 'Memory MB',
|
|
5621
5631
|
totalJobs: 'Total Jobs', enabledJobs: 'Enabled', queueLength: 'Queued', activeJobSessions: 'Running',
|
|
5622
5632
|
weeklyUsage: 'Weekly %', fiveHourRate: '5h Rate %',
|
|
5623
|
-
topicMappings: 'Topics', pendingStalls: '
|
|
5633
|
+
topicMappings: 'Topics', pendingStalls: 'Needs Attention', totalMessages: 'Messages',
|
|
5624
5634
|
proposals: 'Proposals', learnings: 'Learnings', learningsApplied: 'Applied',
|
|
5625
5635
|
gaps: 'Gaps', actions: 'Actions', overdueActions: 'Overdue',
|
|
5626
5636
|
};
|