aicodeman 1.16.6 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -9
- package/dist/session.d.ts +7 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +13 -0
- package/dist/session.js.map +1 -1
- package/dist/types/session.d.ts +10 -0
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js.map +1 -1
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/{app.f9c6aadd.js → app.fe48931f.js} +3 -3
- package/dist/web/public/app.fe48931f.js.br +0 -0
- package/dist/web/public/app.fe48931f.js.gz +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/{constants.53dc0fa1.js → constants.34a17e06.js} +89 -0
- package/dist/web/public/constants.34a17e06.js.br +0 -0
- package/dist/web/public/constants.34a17e06.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/home-sessions.js.gz +0 -0
- package/dist/web/public/i18n.89097ff5.js.gz +0 -0
- package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
- package/dist/web/public/index.html +16 -8
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.0454a3e9.js.gz +0 -0
- package/dist/web/public/mobile-handlers.32fdd57f.js.gz +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/mobile.90287e03.css.gz +0 -0
- package/dist/web/public/notification-manager.5d229063.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/panels-ui.54b0d004.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/readmymind-ui.js.gz +0 -0
- package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/session-lineage.js +174 -0
- package/dist/web/public/session-lineage.js.br +0 -0
- package/dist/web/public/session-lineage.js.gz +0 -0
- package/dist/web/public/session-ui.46a0993c.js.gz +0 -0
- package/dist/web/public/settings-ui.a12588ae.js +67 -0
- package/dist/web/public/settings-ui.a12588ae.js.br +0 -0
- package/dist/web/public/settings-ui.a12588ae.js.gz +0 -0
- package/dist/web/public/{styles.2759a2fd.css → styles.4908bb9b.css} +1 -1
- package/dist/web/public/styles.4908bb9b.css.br +0 -0
- package/dist/web/public/{styles.2759a2fd.css.gz → styles.4908bb9b.css.gz} +0 -0
- package/dist/web/public/{subagent-windows.4cc8b005.js → subagent-windows.2a192c3e.js} +5 -0
- package/dist/web/public/subagent-windows.2a192c3e.js.br +0 -0
- package/dist/web/public/subagent-windows.2a192c3e.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/{terminal-ui.feed829c.js → terminal-ui.5046620b.js} +1 -1
- package/dist/web/public/terminal-ui.5046620b.js.br +0 -0
- package/dist/web/public/terminal-ui.5046620b.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-predictive-echo.6cec0fba.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.4ec670b0.js.gz +0 -0
- package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/route-helpers.d.ts +19 -0
- package/dist/web/route-helpers.d.ts.map +1 -1
- package/dist/web/route-helpers.js +45 -0
- package/dist/web/route-helpers.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +4 -2
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +2 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +14 -0
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +4 -0
- package/dist/web/server.js.map +1 -1
- package/package.json +1 -1
- package/skills/codeman/SKILL.md +755 -242
- package/skills/codeman/reference/endpoints.md +580 -73
- package/skills/codeman/reference/messaging.md +335 -69
- package/skills/codeman/reference/recipes.md +346 -52
- package/dist/web/public/app.f9c6aadd.js.br +0 -0
- package/dist/web/public/app.f9c6aadd.js.gz +0 -0
- package/dist/web/public/constants.53dc0fa1.js.br +0 -0
- package/dist/web/public/constants.53dc0fa1.js.gz +0 -0
- package/dist/web/public/settings-ui.63be4178.js +0 -67
- package/dist/web/public/settings-ui.63be4178.js.br +0 -0
- package/dist/web/public/settings-ui.63be4178.js.gz +0 -0
- package/dist/web/public/styles.2759a2fd.css.br +0 -0
- package/dist/web/public/subagent-windows.4cc8b005.js.br +0 -0
- package/dist/web/public/subagent-windows.4cc8b005.js.gz +0 -0
- package/dist/web/public/terminal-ui.feed829c.js.br +0 -0
- package/dist/web/public/terminal-ui.feed829c.js.gz +0 -0
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
<link rel="manifest" href="manifest.json">
|
|
17
17
|
<title>Codeman</title>
|
|
18
18
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%2360a5fa'/%3E%3Cstop offset='100%25' stop-color='%233b82f6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='32' height='32' rx='6' fill='%230a0a0a'/%3E%3Cpath d='M18 4L8 18h6l-2 10 10-14h-6z' fill='url(%23g)'/%3E%3C/svg%3E">
|
|
19
|
-
<link rel="stylesheet" href="styles.
|
|
19
|
+
<link rel="stylesheet" href="styles.4908bb9b.css">
|
|
20
20
|
<link rel="stylesheet" href="mobile.90287e03.css" media="(max-width: 1023px)">
|
|
21
21
|
<!-- xterm.css loaded async — terminal won't display until xterm.js runs anyway -->
|
|
22
22
|
<link rel="preload" href="vendor/xterm.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
<!-- Preload critical resources — lets browser discover these during HTML parse
|
|
25
25
|
instead of waiting until <script> tags at bottom-of-body are reached. -->
|
|
26
26
|
<link rel="preload" href="vendor/xterm.min.js" as="script">
|
|
27
|
-
<link rel="preload" href="constants.
|
|
28
|
-
<link rel="preload" href="app.
|
|
27
|
+
<link rel="preload" href="constants.34a17e06.js" as="script">
|
|
28
|
+
<link rel="preload" href="app.fe48931f.js" as="script">
|
|
29
29
|
<!-- Self-hosted xterm.js — eliminates CDN DNS/TLS latency (~100ms).
|
|
30
30
|
'defer' preserves execution order (xterm loads before fit addon). -->
|
|
31
31
|
<script defer src="vendor/xterm.min.js"></script>
|
|
@@ -1767,6 +1767,13 @@
|
|
|
1767
1767
|
</div>
|
|
1768
1768
|
<label class="switch switch-sm"><input type="checkbox" id="appSettingsShowTabDetachButton"><span class="slider"></span></label>
|
|
1769
1769
|
</div>
|
|
1770
|
+
<div class="set-row" id="appSettingsLineageLinesItem" data-search="lineage lines spawned worker parent connection">
|
|
1771
|
+
<div class="set-row-text">
|
|
1772
|
+
<span class="set-row-label">Spawn Lineage Lines <span class="set-tag">desktop</span></span>
|
|
1773
|
+
<span class="set-row-desc">Draw a line under the tab strip from a session to the sessions it spawned.</span>
|
|
1774
|
+
</div>
|
|
1775
|
+
<label class="switch switch-sm"><input type="checkbox" id="appSettingsLineageLines" checked><span class="slider"></span></label>
|
|
1776
|
+
</div>
|
|
1770
1777
|
<div class="set-row" id="appSettingsMobileOverviewItem" data-search="overview home screen phone logo">
|
|
1771
1778
|
<div class="set-row-text">
|
|
1772
1779
|
<span class="set-row-label">Overview Home Screen <span class="set-tag">phone</span></span>
|
|
@@ -3171,7 +3178,7 @@
|
|
|
3171
3178
|
<div class="approvals-list"></div>
|
|
3172
3179
|
</div>
|
|
3173
3180
|
|
|
3174
|
-
<script defer src="constants.
|
|
3181
|
+
<script defer src="constants.34a17e06.js"></script>
|
|
3175
3182
|
<script defer src="i18n.89097ff5.js"></script>
|
|
3176
3183
|
<script defer src="mobile-handlers.32fdd57f.js"></script>
|
|
3177
3184
|
<script defer src="voice-input.4ec670b0.js"></script>
|
|
@@ -3180,13 +3187,13 @@
|
|
|
3180
3187
|
<script defer src="input-cjk.63794d0b.js"></script>
|
|
3181
3188
|
<!-- Hardened markdown HTML sanitizer (wires DOMPurify). Must precede app.js. -->
|
|
3182
3189
|
<script defer src="sanitize-html.bc7078d6.js"></script>
|
|
3183
|
-
<script defer src="app.
|
|
3184
|
-
<script defer src="terminal-ui.
|
|
3190
|
+
<script defer src="app.fe48931f.js"></script>
|
|
3191
|
+
<script defer src="terminal-ui.5046620b.js"></script>
|
|
3185
3192
|
<script defer src="respawn-ui.ff0dae4c.js"></script>
|
|
3186
3193
|
<script defer src="ralph-panel.6de2d0f8.js"></script>
|
|
3187
3194
|
<script defer src="orchestrator-panel.js"></script>
|
|
3188
3195
|
<script defer src="cron-ui.js"></script>
|
|
3189
|
-
<script defer src="settings-ui.
|
|
3196
|
+
<script defer src="settings-ui.a12588ae.js"></script>
|
|
3190
3197
|
<script defer src="panels-ui.54b0d004.js"></script>
|
|
3191
3198
|
<script defer src="readmymind-ui.js"></script>
|
|
3192
3199
|
<script defer src="ultracode-panel.js"></script>
|
|
@@ -3199,8 +3206,9 @@
|
|
|
3199
3206
|
<script defer src="entrance-animations.js"></script>
|
|
3200
3207
|
<script defer src="ralph-wizard.13a1831e.js"></script>
|
|
3201
3208
|
<script defer src="api-client.c9b1cddc.js"></script>
|
|
3202
|
-
<script defer src="subagent-windows.
|
|
3209
|
+
<script defer src="subagent-windows.2a192c3e.js"></script>
|
|
3203
3210
|
<script defer src="ultracode-windows.js"></script>
|
|
3211
|
+
<script defer src="session-lineage.js"></script>
|
|
3204
3212
|
<script defer src="image-input.ee16ad88.js"></script>
|
|
3205
3213
|
</body>
|
|
3206
3214
|
</html>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Session lineage lines — the arcs joining a tab to the tabs it spawned.
|
|
3
|
+
*
|
|
4
|
+
* A session that starts another session (the `codeman` agent skill spawning a worker,
|
|
5
|
+
* which passes its own `$CODEMAN_SESSION_ID`) gets `parentSessionId` stamped on its
|
|
6
|
+
* state server-side. This module turns that field into the same kind of glowing
|
|
7
|
+
* connection line the subagent windows use, but tab → tab, so the strip shows at a
|
|
8
|
+
* glance which tab spawned which.
|
|
9
|
+
*
|
|
10
|
+
* It is an ADDITIONAL LAYER on the existing SVG pass, not a second pass: the core
|
|
11
|
+
* `_updateConnectionLinesImmediate()` (subagent-windows.js) calls
|
|
12
|
+
* `_appendLineageConnectionLines(svg, rects)` at its tail, exactly like ultracode does,
|
|
13
|
+
* so every layer shares ONE batched read → write reflow and one tab-rect cache.
|
|
14
|
+
*
|
|
15
|
+
* Two constraints that are not obvious from the code:
|
|
16
|
+
* - DESKTOP ONLY. The overlay is `z-index: 999`; the desktop header is 100 (arcs paint
|
|
17
|
+
* over it, which is what lets them touch tab bottoms), but under 1024px mobile.css
|
|
18
|
+
* makes the header `position: fixed; z-index: 1200` and would bury them. The phone
|
|
19
|
+
* strip is also a scroller where both endpoints are rarely on screen at once.
|
|
20
|
+
* - Paths carry `data-agent-id="lineage:<childId>"` because that is the attribute
|
|
21
|
+
* `_applyLineEntrances()` queries, so the draw-in animation and its
|
|
22
|
+
* negative-`animation-delay` resume across `svg.innerHTML = ''` come for free.
|
|
23
|
+
*
|
|
24
|
+
* @mixin Extends CodemanApp.prototype via Object.assign
|
|
25
|
+
* @dependency subagent-windows.js (_updateConnectionLinesImmediate, #connectionLines)
|
|
26
|
+
* @dependency constants.js (window.CodemanLineage.computePath)
|
|
27
|
+
* @dependency settings-ui.js (loadAppSettingsFromStorage, getDefaultSettings)
|
|
28
|
+
* @loadorder 15.6 (after ultracode-windows.js — appended to the same SVG pass)
|
|
29
|
+
*/
|
|
30
|
+
/* global CodemanApp, MobileDetection */
|
|
31
|
+
|
|
32
|
+
Object.assign(CodemanApp.prototype, {
|
|
33
|
+
/**
|
|
34
|
+
* Per-device opt-out (App Settings → Appearance), cached because the draw path runs
|
|
35
|
+
* on every tab render, scroll and resize. `applyLineageLineSettings()` refreshes it.
|
|
36
|
+
*
|
|
37
|
+
* Desktop-only for the z-index reason in the file header, and gated on device type
|
|
38
|
+
* rather than on the settings namespace: this is a layout decision, like the phone
|
|
39
|
+
* overview's `shouldUseMobileOverview()`.
|
|
40
|
+
*/
|
|
41
|
+
_lineageLinesEnabled() {
|
|
42
|
+
if (this._lineageLinesOn === undefined) this._syncLineageLinesEnabled();
|
|
43
|
+
return this._lineageLinesOn;
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
_syncLineageLinesEnabled() {
|
|
47
|
+
let on = false;
|
|
48
|
+
try {
|
|
49
|
+
if (MobileDetection.getDeviceType() === 'desktop') {
|
|
50
|
+
const settings = this.loadAppSettingsFromStorage ? this.loadAppSettingsFromStorage() : {};
|
|
51
|
+
const defaults = this.getDefaultSettings ? this.getDefaultSettings() : {};
|
|
52
|
+
on = settings.sessionLineageLines ?? defaults.sessionLineageLines ?? true;
|
|
53
|
+
}
|
|
54
|
+
} catch (_e) {
|
|
55
|
+
on = false;
|
|
56
|
+
}
|
|
57
|
+
this._lineageLinesOn = !!on;
|
|
58
|
+
return this._lineageLinesOn;
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
/** Re-read the setting and redraw. Called from the settings apply pass and on resize. */
|
|
62
|
+
applyLineageLineSettings() {
|
|
63
|
+
const prev = this._lineageLinesOn;
|
|
64
|
+
const next = this._syncLineageLinesEnabled();
|
|
65
|
+
if (prev !== next) this.updateConnectionLines();
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Every parent → child pair worth drawing, with the child's index among its siblings
|
|
70
|
+
* (that index is what nests sibling arcs instead of overprinting them).
|
|
71
|
+
*
|
|
72
|
+
* Walks `sessionOrder` rather than the sessions Map so sibling depth follows the
|
|
73
|
+
* strip's own left-to-right order, which is what the user sees.
|
|
74
|
+
*/
|
|
75
|
+
_collectLineageEdges() {
|
|
76
|
+
const edges = [];
|
|
77
|
+
if (!this.sessions || this.sessions.size < 2) return edges;
|
|
78
|
+
const order = this.sessionOrder && this.sessionOrder.length ? this.sessionOrder : [...this.sessions.keys()];
|
|
79
|
+
const seenPerParent = new Map();
|
|
80
|
+
for (const id of order) {
|
|
81
|
+
const session = this.sessions.get(id);
|
|
82
|
+
const parentId = session && session.parentSessionId;
|
|
83
|
+
// A parent that is gone (closed, or never came back after a restart) draws
|
|
84
|
+
// nothing: the field is decoration, so a dangling one is simply not rendered.
|
|
85
|
+
if (!parentId || parentId === id || !this.sessions.has(parentId)) continue;
|
|
86
|
+
const depth = seenPerParent.get(parentId) || 0;
|
|
87
|
+
seenPerParent.set(parentId, depth + 1);
|
|
88
|
+
edges.push({ parentId, childId: id, depth, status: session.status || 'idle' });
|
|
89
|
+
}
|
|
90
|
+
return edges;
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Append the lineage layer to the shared SVG pass.
|
|
95
|
+
*
|
|
96
|
+
* Contract with the caller: `rects` is the batched read cache keyed `tab:<id>`, and
|
|
97
|
+
* everything read here goes through it so a tab another layer already measured is
|
|
98
|
+
* never measured twice. All reads happen before any append, keeping the caller's
|
|
99
|
+
* read → write split intact.
|
|
100
|
+
*/
|
|
101
|
+
_appendLineageConnectionLines(svg, rects) {
|
|
102
|
+
this._lineageEdgeCount = 0;
|
|
103
|
+
if (!svg || !this._lineageLinesEnabled()) return;
|
|
104
|
+
const compute = window.CodemanLineage && window.CodemanLineage.computePath;
|
|
105
|
+
if (!compute) return;
|
|
106
|
+
|
|
107
|
+
const edges = this._collectLineageEdges();
|
|
108
|
+
if (edges.length === 0) return;
|
|
109
|
+
this._lineageEdgeCount = edges.length;
|
|
110
|
+
if (!rects) rects = new Map();
|
|
111
|
+
|
|
112
|
+
// PHASE 1 — reads.
|
|
113
|
+
const strip = document.getElementById('sessionTabs');
|
|
114
|
+
if (!strip) return;
|
|
115
|
+
const stripRect = strip.getBoundingClientRect();
|
|
116
|
+
for (const edge of edges) {
|
|
117
|
+
for (const id of [edge.parentId, edge.childId]) {
|
|
118
|
+
const key = 'tab:' + id;
|
|
119
|
+
if (rects.has(key)) continue;
|
|
120
|
+
const tab = strip.querySelector(`.session-tab[data-id="${CSS.escape(id)}"]`);
|
|
121
|
+
rects.set(key, tab ? tab.getBoundingClientRect() : null);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// PHASE 2 — writes, from the cache only.
|
|
126
|
+
for (const edge of edges) {
|
|
127
|
+
const parentRect = rects.get('tab:' + edge.parentId);
|
|
128
|
+
const childRect = rects.get('tab:' + edge.childId);
|
|
129
|
+
if (!parentRect || !childRect) continue;
|
|
130
|
+
|
|
131
|
+
const geom = compute({ parent: parentRect, child: childRect, strip: stripRect, depth: edge.depth });
|
|
132
|
+
if (!geom) continue; // scrolled out of the strip, or a degenerate rect
|
|
133
|
+
|
|
134
|
+
const line = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
135
|
+
line.setAttribute('d', geom.d);
|
|
136
|
+
// The working class marches the dashes, so an active worker is visible along
|
|
137
|
+
// the line itself. `status` is the CHILD's, which is the interesting end.
|
|
138
|
+
const working = edge.status === 'working' ? ' lineage-line--working' : '';
|
|
139
|
+
line.setAttribute('class', 'connection-line lineage-line' + working);
|
|
140
|
+
// `data-agent-id` is what _applyLineEntrances() queries — see the file header.
|
|
141
|
+
line.setAttribute('data-agent-id', 'lineage:' + edge.childId);
|
|
142
|
+
line.setAttribute('data-parent-tab', edge.parentId);
|
|
143
|
+
line.setAttribute('data-child-tab', edge.childId);
|
|
144
|
+
svg.appendChild(line);
|
|
145
|
+
|
|
146
|
+
// Direction marker at the CHILD end. A circle rather than an SVG <marker>:
|
|
147
|
+
// markers need a <defs> block and fight the dash pattern.
|
|
148
|
+
const dot = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
149
|
+
dot.setAttribute('cx', String(geom.endX));
|
|
150
|
+
dot.setAttribute('cy', String(geom.endY));
|
|
151
|
+
dot.setAttribute('r', '3');
|
|
152
|
+
dot.setAttribute('class', 'lineage-line-dot' + working);
|
|
153
|
+
dot.setAttribute('data-child-tab', edge.childId);
|
|
154
|
+
svg.appendChild(dot);
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The strip scrolls (desktop `overflow-x: auto` and every wrapped layout), and a
|
|
160
|
+
* scroll moves both endpoints without firing any render, so the arcs would slide off
|
|
161
|
+
* their tabs. Passive listener, and the redraw is the normal coalesced one.
|
|
162
|
+
*
|
|
163
|
+
* Installed once; the guard also keeps a re-init from stacking listeners.
|
|
164
|
+
*/
|
|
165
|
+
_installLineageStripScrollListener() {
|
|
166
|
+
if (this._lineageScrollHandler) return;
|
|
167
|
+
const strip = document.getElementById('sessionTabs');
|
|
168
|
+
if (!strip) return;
|
|
169
|
+
this._lineageScrollHandler = () => {
|
|
170
|
+
if (this._lineageEdgeCount > 0) this.updateConnectionLines();
|
|
171
|
+
};
|
|
172
|
+
strip.addEventListener('scroll', this._lineageScrollHandler, { passive: true });
|
|
173
|
+
},
|
|
174
|
+
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";Object.assign(CodemanApp.prototype,{_onHookIdlePrompt(e){e.sessionId&&this.setPendingHook(e.sessionId,"idle_prompt"),this._notifySession(e.sessionId,"warning","hook-idle","Waiting for Input",e.message||"Claude is idle and waiting for a prompt")},_onHookPermissionPrompt(e){e.sessionId&&this.setPendingHook(e.sessionId,"permission_prompt");const t=e.tool?`${e.tool}${e.command?": "+e.command:e.file?": "+e.file:""}`:"";this._notifySession(e.sessionId,"critical","hook-permission","Permission Required",t||"Claude needs tool approval to continue")},_onHookElicitationDialog(e){e.sessionId&&this.setPendingHook(e.sessionId,"elicitation_dialog"),this._notifySession(e.sessionId,"critical","hook-elicitation","Question Asked",e.question||"Claude is asking a question and waiting for your answer")},_onHookElicitationComplete(e){e.sessionId&&this.clearPendingHooks(e.sessionId,"elicitation_dialog")},_onHookElicitationResponse(e){this._onHookElicitationComplete(e)},_onHookStop(e){e.sessionId&&this.clearPendingHooks(e.sessionId),this._notifySession(e.sessionId,"info","hook-stop","Response Complete",e.reason||"Claude has finished responding")},_onHookTeammateIdle(e){const t=this.sessions.get(e.sessionId);this._notifySession(e.sessionId,"warning","hook-teammate-idle","Teammate Idle",`A teammate is idle in ${t?.name||e.sessionId}`)},_onHookTaskCompleted(e){const t=this.sessions.get(e.sessionId);this._notifySession(e.sessionId,"info","hook-task-completed","Task Completed",`A team task completed in ${t?.name||e.sessionId}`)},_onTunnelStarted(e){console.log("[Tunnel] Started:",e.url),this._tunnelUrl=e.url,this._dismissTunnelConnecting(),this._updateTunnelUrlDisplay(e.url),this._updateTunnelIndicator(!0),document.getElementById("welcomeOverlay")?.classList.contains("visible")?(this._updateWelcomeTunnelBtn(!0,e.url,!0),this.showToast("Tunnel active","success")):(this._updateWelcomeTunnelBtn(!0,e.url),this.showToast(`Tunnel active: ${e.url}`,"success"),this.showTunnelQR())},_onTunnelStopped(){console.log("[Tunnel] Stopped"),this._tunnelUrl=null,this._dismissTunnelConnecting(),this._updateTunnelUrlDisplay(null),this._updateWelcomeTunnelBtn(!1),this._updateTunnelIndicator(!1),this.closeTunnelPanel(),this.closeTunnelQR()},_onTunnelProgress(e){console.log("[Tunnel] Progress:",e.message);const t=document.getElementById("tunnelConnectingToast");t&&(t.innerHTML=`<span class="tunnel-spinner"></span> ${e.message}`);const n=document.getElementById("welcomeTunnelBtn");n?.classList.contains("connecting")&&(n.innerHTML=`<span class="tunnel-spinner"></span> ${e.message}`)},_onTunnelError(e){console.warn("[Tunnel] Error:",e.message),this._dismissTunnelConnecting(),this.showToast(`Tunnel error: ${e.message}`,"error");const t=document.getElementById("welcomeTunnelBtn");t&&(t.disabled=!1,t.classList.remove("connecting"))},_onTunnelQrRotated(e){if(e.svg){const t=document.getElementById("tunnelQrContainer");t&&(t.innerHTML=e.svg);const n=document.getElementById("welcomeQrInner");n&&(n.innerHTML=e.svg)}else this._refreshTunnelQrFromApi();this._resetQrCountdown()},_onTunnelQrRegenerated(e){if(e.svg){const t=document.getElementById("tunnelQrContainer");t&&(t.innerHTML=e.svg);const n=document.getElementById("welcomeQrInner");n&&(n.innerHTML=e.svg)}else this._refreshTunnelQrFromApi();this._resetQrCountdown()},_onTunnelQrAuthUsed(e){const n=(e.ua||"Unknown device").match(/Chrome|Firefox|Safari|Edge|Mobile/)?.[0]||"Browser";this.showToast(`Device authenticated via QR (${n}, ${e.ip}). Not you?`,"warning",{duration:1e4,action:{label:"Revoke All",onClick:()=>{fetch("/api/auth/revoke",{method:"POST",headers:{"Content-Type":"application/json"},body:"{}"}).then(()=>this.showToast("All sessions revoked","success")).catch(()=>this.showToast("Failed to revoke sessions","error"))}}})},registerServiceWorker(){"serviceWorker"in navigator&&navigator.serviceWorker.register("/sw.js").then(e=>{this._swRegistration=e,navigator.serviceWorker.addEventListener("message",t=>{if(t.data?.type==="notification-click"){const{sessionId:n,action:s,approvalId:o}=t.data;s?this.handleNotificationAction?.(s,o,n):n&&this.sessions.has(n)&&this.selectSession(n),window.focus()}}),e.pushManager.getSubscription().then(t=>{t&&(this._pushSubscription=t,this._updatePushUI(!0))})}).catch(()=>{})},async subscribeToPush(){if(!this._swRegistration){this.showToast("Service worker not available. HTTPS or localhost required.","error");return}try{const e=await this._apiJson("/api/push/vapid-key");if(!e)throw new Error("Failed to get VAPID key");const t=urlBase64ToUint8Array(e.publicKey),n=await this._swRegistration.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:t}),s=n.toJSON(),o=await this._apiJson("/api/push/subscribe",{method:"POST",body:{endpoint:s.endpoint,keys:s.keys,userAgent:navigator.userAgent,pushPreferences:this._buildPushPreferences()}});if(!o)throw new Error("Failed to register subscription");this._pushSubscription=n,this._pushSubscriptionId=o.id,localStorage.setItem("codeman-push-subscription-id",o.id),this._updatePushUI(!0),this.showToast("Push notifications enabled","success")}catch(e){this.showToast("Push subscription failed: "+(e.message||e),"error")}},async unsubscribeFromPush(){try{this._pushSubscription&&await this._pushSubscription.unsubscribe();const e=this._pushSubscriptionId||localStorage.getItem("codeman-push-subscription-id");e&&await fetch(`/api/push/subscribe/${e}`,{method:"DELETE"}).catch(()=>{}),this._pushSubscription=null,this._pushSubscriptionId=null,localStorage.removeItem("codeman-push-subscription-id"),this._updatePushUI(!1),this.showToast("Push notifications disabled","success")}catch(e){this.showToast("Failed to unsubscribe: "+(e.message||e),"error")}},async togglePushSubscription(){this._pushSubscription?await this.unsubscribeFromPush():await this.subscribeToPush()},async _syncPushPreferences(){const e=this._pushSubscriptionId||localStorage.getItem("codeman-push-subscription-id");if(e)try{await fetch(`/api/push/subscribe/${e}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({pushPreferences:this._buildPushPreferences()})})}catch{}},_buildPushPreferences(){const e={},t={"hook:permission_prompt":"eventPermissionPush","hook:elicitation_dialog":"eventQuestionPush","hook:idle_prompt":"eventIdlePush","hook:stop":"eventStopPush","respawn:blocked":"eventRespawnPush","session:ralphCompletionDetected":"eventRalphPush"};for(const[n,s]of Object.entries(t)){const o=document.getElementById(s);e[n]=o?o.checked:!0}return e["session:error"]=!0,e},_updatePushUI(e){const t=document.getElementById("pushSubscribeBtn"),n=document.getElementById("pushSubscriptionStatus");t&&(t.textContent=e?"Unsubscribe":"Subscribe"),n&&(n.textContent=e?"active":"off",n.classList.remove("granted","denied"),e&&n.classList.add("granted"))},openAppSettings(){const e=this.loadAppSettingsFromStorage();document.getElementById("appSettingsDisplayName").value=e.displayName||"Codeman",document.getElementById("appSettingsLanguage").value=e.language==="zh-CN"?"zh-CN":"en",document.getElementById("appSettingsClaudeMdPath").value=e.defaultClaudeMdPath||"",document.getElementById("appSettingsDefaultDir").value=e.defaultWorkingDir||"";const t=this.getDefaultSettings();document.getElementById("appSettingsRalphEnabled").checked=e.ralphTrackerEnabled??t.ralphTrackerEnabled??!1,document.getElementById("appSettingsShowFontControls").checked=e.showFontControls??t.showFontControls??!1,document.getElementById("appSettingsShowSystemStats").checked=e.showSystemStats??t.showSystemStats??!0,document.getElementById("appSettingsShowLifecycleLog").checked=e.showLifecycleLog??t.showLifecycleLog??!1,document.getElementById("appSettingsShowResponseViewer").checked=e.showResponseViewer??t.showResponseViewer??!1,document.getElementById("appSettingsShowFileViewerButton").checked=e.showFileViewerButton??t.showFileViewerButton??!0,document.getElementById("appSettingsShowAttachmentsButton").checked=e.showAttachmentsButton??t.showAttachmentsButton??!1,document.getElementById("appSettingsSkin").value=e.skin??t.skin??"daylight-blue",this._syncEntranceAnimSetting?.(),document.getElementById("appSettingsWebglRenderer").checked=e.webglRendererEnabled??t.webglRendererEnabled??!0;const n=document.getElementById("appSettingsWebglRendererItem");n&&(n.style.display=MobileDetection.getDeviceType()==="desktop"?"":"none"),document.getElementById("appSettingsShowMonitor").checked=e.showMonitor??t.showMonitor??!1,document.getElementById("appSettingsShowProjectInsights").checked=e.showProjectInsights??t.showProjectInsights??!1,document.getElementById("appSettingsShowFileBrowser").checked=e.showFileBrowser??t.showFileBrowser??!1,document.getElementById("appSettingsShowSubagents").checked=e.showSubagents??t.showSubagents??!1,document.getElementById("appSettingsShowUltracodeAgents").checked=e.showUltracodeAgents??t.showUltracodeAgents??!1,document.getElementById("appSettingsApprovalsInbox").checked=e.approvalsInboxEnabled===!0,document.getElementById("appSettingsReadMyMind").checked=e.readMyMindEnabled===!0,document.getElementById("appSettingsUltracodeFloatingWindows").checked=e.ultracodeFloatingWindows??t.ultracodeFloatingWindows??!1,document.getElementById("appSettingsShowMultiMonitorButton").checked=e.showMultiMonitorButton??t.showMultiMonitorButton??!1,document.getElementById("appSettingsShowPlanUsageLimits").checked=this.planUsageChipEnabled(e),document.getElementById("appSettingsShowRedrawButton").checked=e.showRedrawButton??t.showRedrawButton??!1,document.getElementById("appSettingsLineageLines").checked=e.sessionLineageLines??t.sessionLineageLines??!0;const s=document.getElementById("appSettingsLineageLinesItem");s&&(s.style.display=MobileDetection.getDeviceType()==="desktop"?"":"none"),document.getElementById("appSettingsMobileOverview").checked=e.mobileOverviewEnabled??t.mobileOverviewEnabled??!1;const o=document.getElementById("appSettingsMobileOverviewItem");o&&(o.style.display=MobileDetection.getDeviceType()==="mobile"?"":"none"),document.getElementById("appSettingsShowSessionButton").checked=e.showSessionButton??t.showSessionButton??!1,document.getElementById("appSettingsShowAwayDigestButton").checked=e.showAwayDigestButton??t.showAwayDigestButton??!1,document.getElementById("appSettingsShowCronButton").checked=e.showCronButton??t.showCronButton??!1;const i=document.getElementById("appSettingsGestureControlItem");i&&(i.style.display=window.__codemanGestureAvailable?"":"none"),document.getElementById("appSettingsGestureControl").checked=e.gestureControlEnabled??t.gestureControlEnabled??!1,document.getElementById("appSettingsSubagentTracking").checked=e.subagentTrackingEnabled??t.subagentTrackingEnabled??!0,document.getElementById("appSettingsSubagentActiveTabOnly").checked=e.subagentActiveTabOnly??t.subagentActiveTabOnly??!0,document.getElementById("appSettingsImageWatcherEnabled").checked=e.imageWatcherEnabled??t.imageWatcherEnabled??!1,document.getElementById("appSettingsTunnelEnabled").checked=e.tunnelEnabled??!1,this.loadTunnelStatus(),document.getElementById("appSettingsLocalEcho").checked=e.localEchoEnabled??MobileDetection.isTouchDevice(),document.getElementById("appSettingsTerminalWheelLocal").checked=e.terminalWheelLocalScrollback??t.terminalWheelLocalScrollback??!1,document.getElementById("appSettingsCjkInput").checked=e.cjkInputEnabled??t.cjkInputEnabled??!1,document.getElementById("appSettingsExtendedKeyboardBar").checked=e.extendedKeyboardBar??!1,document.getElementById("appSettingsTabTwoRows").checked=e.tabTwoRows??t.tabTwoRows??!1,document.getElementById("appSettingsShowTabDetachButton").checked=e.showTabDetachButton??t.showTabDetachButton??!1;const a=document.getElementById("appSettingsClaudeMode"),l=document.getElementById("allowedToolsRow");a.value=e.claudeMode||"dangerously-skip-permissions",document.getElementById("appSettingsAllowedTools").value=e.allowedTools||"",l.style.display=a.value==="allowedTools"?"":"none",a.onchange=()=>{l.style.display=a.value==="allowedTools"?"":"none"},document.getElementById("appSettingsCodexDangerouslyBypassApprovals").checked=e.codexDangerouslyBypassApprovals??!1,document.getElementById("appSettingsCodexAnimations").checked=e.codexAnimationsEnabled??!1,this._applyCodexSettingsVisibility(),document.getElementById("appSettingsAgentTeams").checked=e.agentTeamsEnabled??!1,document.getElementById("appSettingsAgentSkill").checked=e.agentSkillEnabled??!1,document.getElementById("appSettingsClaudeModel").value=e.claudeModel??"",document.getElementById("appSettingsOpusContext1m").checked=e.opusContext1mEnabled??!1,document.getElementById("appSettingsRemoteAutoReconnect").checked=e.remoteAutoReconnect??!0,document.getElementById("appSettingsThinkingEffort").value=e.thinkingEffort??"";const c=e.nice||{};document.getElementById("appSettingsNiceEnabled").checked=c.enabled??!1,document.getElementById("appSettingsNiceValue").value=c.niceValue??10,this.loadModelConfigForSettings();const d=this.notificationManager?.preferences||{};document.getElementById("appSettingsNotifEnabled").checked=d.enabled??!0,document.getElementById("appSettingsNotifBrowser").checked=d.browserNotifications??!1,document.getElementById("appSettingsNotifAudio").checked=d.audioAlerts??!1,document.getElementById("appSettingsNotifStuckMins").value=Math.round((d.stuckThresholdMs||6e5)/6e4),document.getElementById("appSettingsNotifCritical").checked=!d.muteCritical,document.getElementById("appSettingsNotifWarning").checked=!d.muteWarning,document.getElementById("appSettingsNotifInfo").checked=!d.muteInfo,document.getElementById("appSettingsPushEnabled").checked=!!this._pushSubscription,this._updatePushUI(!!this._pushSubscription);const r=d.eventTypes||{},u=r.permission_prompt||{};document.getElementById("eventPermissionEnabled").checked=u.enabled??!0,document.getElementById("eventPermissionBrowser").checked=u.browser??!0,document.getElementById("eventPermissionPush").checked=u.push??!1,document.getElementById("eventPermissionAudio").checked=u.audio??!0;const g=r.elicitation_dialog||{};document.getElementById("eventQuestionEnabled").checked=g.enabled??!0,document.getElementById("eventQuestionBrowser").checked=g.browser??!0,document.getElementById("eventQuestionPush").checked=g.push??!1,document.getElementById("eventQuestionAudio").checked=g.audio??!0;const p=r.idle_prompt||{};document.getElementById("eventIdleEnabled").checked=p.enabled??!0,document.getElementById("eventIdleBrowser").checked=p.browser??!0,document.getElementById("eventIdlePush").checked=p.push??!1,document.getElementById("eventIdleAudio").checked=p.audio??!1;const S=r.stop||{};document.getElementById("eventStopEnabled").checked=S.enabled??!1,document.getElementById("eventStopBrowser").checked=S.browser??!1,document.getElementById("eventStopPush").checked=S.push??!1,document.getElementById("eventStopAudio").checked=S.audio??!1;const y=r.respawn_cycle||{};document.getElementById("eventRespawnEnabled").checked=y.enabled??!0,document.getElementById("eventRespawnBrowser").checked=y.browser??!1,document.getElementById("eventRespawnPush").checked=y.push??!1,document.getElementById("eventRespawnAudio").checked=y.audio??!1;const b=r.ralph_complete||{};document.getElementById("eventRalphEnabled").checked=b.enabled??!0,document.getElementById("eventRalphBrowser").checked=b.browser??!0,document.getElementById("eventRalphPush").checked=b.push??!1,document.getElementById("eventRalphAudio").checked=b.audio??!0;const f=r.subagent_spawn||{};document.getElementById("eventSubagentEnabled").checked=f.enabled??!1,document.getElementById("eventSubagentBrowser").checked=f.browser??!1,document.getElementById("eventSubagentPush").checked=f.push??!1,document.getElementById("eventSubagentAudio").checked=f.audio??!1;const w=document.getElementById("notifPermissionStatus");if(w&&typeof Notification<"u"){const m=Notification.permission;w.textContent=m==="granted"?"\u2713":m==="denied"?"\u2717":"?",w.classList.remove("granted","denied"),m==="granted"?w.classList.add("granted"):m==="denied"&&w.classList.add("denied")}const h=VoiceInput._getDeepgramConfig();document.getElementById("voiceDeepgramKey").value=h.apiKey||"",document.getElementById("voiceLanguage").value=h.language||"en-US",document.getElementById("voiceKeyterms").value=h.keyterms||DEFAULT_VOICE_KEYTERMS,document.getElementById("voiceInsertMode").value=h.insertMode||"direct",document.getElementById("voiceProvider").value=h.provider||"auto",document.getElementById("appSettingsClaudeVoice").checked=e.claudeVoiceEnabled??!1;const B=document.getElementById("voiceDeepgramKey");B.type="password",document.getElementById("voiceKeyToggleBtn").textContent="Show",this._renderVoiceProviderStatus(),VoiceInput.refreshClaudeStatus().then(()=>this._renderVoiceProviderStatus()),this._initUpdatesSection(),this._initSettingsNav(),this._syncSettingsChips(),this._syncModelCards(),this._syncEffortSegment(),this.switchSettingsTab("settings-updates");const v=document.getElementById("appSettingsModal");v.classList.add("active"),this.activeFocusTrap=new FocusTrap(v),this.activeFocusTrap.activate()},_applyCodexSettingsVisibility(){const e=document.getElementById("appSettingsCodexGroup");e&&(e.style.display=window.__codemanCliAvailable?.codex===!0?"":"none")},switchSettingsTab(e){e==="settings-shortcuts"&&this.renderShortcutSettingsList?.();const t=document.getElementById("appSettingsDoc"),n=document.getElementById(e);if(t&&n&&typeof n.offsetTop=="number"){const s=document.getElementById("appSettingsJump"),o=s&&s.offsetParent?s.offsetHeight+16:6;t.scrollTop=Math.max(0,n.offsetTop-o)}this._setActiveSettingsSection(e)},_setActiveSettingsSection(e){const t=document.getElementById("appSettingsModal");if(!t||typeof t.querySelectorAll!="function")return;let n=null;t.querySelectorAll(".set-rail-item").forEach(a=>{const l=a.dataset.section===e;a.classList.toggle("active",l),l&&(n=a)}),t.querySelectorAll(".set-jump-row").forEach(a=>{a.classList.toggle("active",a.dataset.section===e)});const s=document.getElementById("appSettingsJump")?.querySelector(".set-jump-label");s&&n&&(s.textContent=n.textContent.trim());const o=document.getElementById("appSettingsJump")?.querySelector(".set-jump-ico"),i=n?.querySelector("svg");o&&i&&(o.innerHTML=i.innerHTML)},_initSettingsNav(){const e=document.getElementById("appSettingsModal"),t=document.getElementById("appSettingsDoc");if(!e||!t||typeof e.querySelectorAll!="function"||(this._buildModelCards(),this._buildEffortSegment(),this._buildSettingsJumpMenu(),e.dataset.navReady==="1"))return;e.dataset.navReady="1",e.querySelector(".set-rail-items")?.addEventListener("click",i=>{const a=i.target.closest?.(".set-rail-item");a?.dataset.section&&this.switchSettingsTab(a.dataset.section)}),document.getElementById("appSettingsJumpMenu")?.addEventListener("click",i=>{const a=i.target.closest?.(".set-jump-row");a?.dataset.section&&(this._toggleSettingsJump(!1),this.switchSettingsTab(a.dataset.section))}),document.getElementById("appSettingsJump")?.addEventListener("click",()=>this._toggleSettingsJump()),document.getElementById("appSettingsJumpVeil")?.addEventListener("click",()=>this._toggleSettingsJump(!1)),t.addEventListener("scroll",()=>{this._settingsSpyQueued||(this._settingsSpyQueued=!0,requestAnimationFrame(()=>{this._settingsSpyQueued=!1;const i=[...t.querySelectorAll(".set-section")].filter(l=>l.offsetParent!==null);if(!i.length)return;let a=i[0].id;for(const l of i)l.offsetTop-t.scrollTop<=140&&(a=l.id);this._setActiveSettingsSection(a)}))});const n=document.getElementById("appSettingsSearch");n?.addEventListener("input",()=>this._filterSettings(n.value)),e.querySelectorAll(".set-chip input").forEach(i=>{i.addEventListener("change",()=>this._syncSettingsChips())});const s=e.querySelector(".set-group-head-toggle"),o=s?.closest(".set-group-advanced");if(s&&o){const i=()=>{const a=o.classList.toggle("open");s.setAttribute("aria-expanded",a?"true":"false")};s.addEventListener("click",i),s.addEventListener("keydown",a=>{(a.key==="Enter"||a.key===" ")&&(a.preventDefault(),i())})}document.getElementById("appSettingsOpusContext1m")?.addEventListener("change",()=>this._applyModelSelection())},_buildSettingsJumpMenu(){const e=document.getElementById("appSettingsModal"),t=document.getElementById("appSettingsJumpMenu");!e||!t||(t.innerHTML="",e.querySelectorAll(".set-rail-item").forEach(n=>{const s=document.createElement("button");s.type="button",s.className="set-jump-row",s.dataset.section=n.dataset.section,s.innerHTML=n.innerHTML;const o=document.getElementById(n.dataset.section),i=o?o.querySelectorAll("input, select").length:0;if(i){const a=document.createElement("span");a.className="set-jump-count",a.textContent=String(i),s.appendChild(a)}t.appendChild(s)}))},_toggleSettingsJump(e){const t=document.getElementById("appSettingsModal");if(!t)return;const n=e===void 0?!t.classList.contains("jump-open"):e;t.classList.toggle("jump-open",n),document.getElementById("appSettingsJump")?.setAttribute("aria-expanded",n?"true":"false")},_syncSettingsChips(){document.querySelectorAll("#appSettingsModal .set-chip, #sessionOptionsModal .set-chip").forEach(e=>{e.classList.toggle("is-on",!!e.querySelector("input")?.checked)}),this._syncLayoutPreview()},_syncLayoutPreview(){const e=document.getElementById("appSettingsModal");if(!e||typeof e.querySelectorAll!="function")return;const t={header:document.getElementById("appSettingsPreviewHeader"),panel:document.getElementById("appSettingsPreviewPanels"),toolbar:document.getElementById("appSettingsPreviewToolbar"),float:document.getElementById("appSettingsPreviewFloats")};if(!t.header)return;Object.values(t).forEach(i=>{i&&(i.innerHTML="")});const n=[...e.querySelectorAll(".set-chip[data-preview]")].filter(i=>i.querySelector("input")?.checked).sort((i,a)=>(Number(i.dataset.previewOrder)||0)-(Number(a.dataset.previewOrder)||0));let s=0;for(const i of n){const a=i.dataset.preview,l=t[a];if(!l)continue;const c=i.querySelectorAll("span"),d=(c[c.length-1]?.textContent||"").trim(),r=document.createElement("span");if(r.title=d,a==="header"){const u=i.dataset.previewText;r.className=u?"set-preview-chip":"set-preview-btn",u?r.textContent=u:this._appendPreviewIcon(r,i)}else{r.className=`set-preview-${a}`,this._appendPreviewIcon(r,i);const u=document.createElement("span");u.textContent=d,r.appendChild(u)}l.appendChild(r),s++}const o=document.getElementById("appSettingsPreviewEmpty");o&&(o.hidden=s>0)},_appendPreviewIcon(e,t){const n=t.querySelector(".set-chip-ico");if(!n)return;const s=n.cloneNode(!0);s.classList.remove("set-chip-ico"),s.classList.add("set-preview-ico"),e.appendChild(s)},_buildModelCards(){const e=document.getElementById("appSettingsClaudeModel"),t=document.getElementById("appSettingsModelCards");!e||!t||t.dataset.built==="1"||!e.options||(t.innerHTML="",[...e.options].filter(n=>n.dataset.variant!=="1m").forEach(n=>{const s=document.createElement("button");s.type="button",s.className="set-modelcard",s.setAttribute("role","radio"),s.dataset.value=n.value,n.dataset.ctx==="1"&&(s.dataset.ctx="1");const o=document.createElement("span");o.className="set-mc-top";const i=document.createElement("span");i.className="set-mc-name",i.textContent=n.textContent,o.appendChild(i);const a=document.createElement("span");a.className="set-mc-dot",o.appendChild(a),s.appendChild(o);const l=document.createElement("span");if(l.className="set-mc-meta",l.textContent=n.dataset.meta||"",s.appendChild(l),n.dataset.ctx==="1"){const c=document.createElement("span");c.className="set-mc-ctx",c.textContent="1M capable",s.appendChild(c)}s.addEventListener("click",()=>{this._settingsModelBase=n.value,this._applyModelSelection()}),t.appendChild(s)}),t.dataset.built="1")},_syncModelCards(){const e=document.getElementById("appSettingsClaudeModel");if(!e)return;const t=e.value||"";if(this._settingsModelBase=t.endsWith("[1m]")?t.slice(0,-4):t,t.endsWith("[1m]")){const n=document.getElementById("appSettingsOpusContext1m");n&&(n.checked=!0)}this._applyModelSelection()},_applyModelSelection(){const e=document.getElementById("appSettingsClaudeModel"),t=document.getElementById("appSettingsModelCards");if(!e||!t)return;const n=this._settingsModelBase||"";let s=!1;t.querySelectorAll(".set-modelcard").forEach(c=>{const d=c.dataset.value===n;c.classList.toggle("selected",d),c.setAttribute("aria-checked",d?"true":"false"),d&&(s=c.dataset.ctx==="1")});const o=!!document.getElementById("appSettingsOpusContext1m")?.checked;e.value=n&&s&&o?`${n}[1m]`:n;const i=document.getElementById("appSettingsContextRow"),a=document.getElementById("appSettingsContextDesc"),l=!!n&&!s;i?.classList.toggle("set-row-disabled",l),a&&(a.textContent=l?"The selected model has no 1M variant.":n?"Available for Fable 5, Opus and Opus 4.6.":"With no model pinned, this starts new sessions on Opus with a 1M window.")},_buildEffortSegment(){const e=document.getElementById("appSettingsThinkingEffort"),t=document.getElementById("appSettingsEffortSegment");!e||!t||t.dataset.built==="1"||!e.options||(t.innerHTML="",[...e.options].forEach(n=>{const s=document.createElement("button");s.type="button",s.setAttribute("role","radio"),s.dataset.value=n.value,s.textContent=n.textContent,s.addEventListener("click",()=>{e.value=n.value,this._syncEffortSegment()}),t.appendChild(s)}),t.dataset.built="1")},_syncEffortSegment(){const e=document.getElementById("appSettingsThinkingEffort"),t=document.getElementById("appSettingsEffortSegment");!e||!t||t.querySelectorAll("button").forEach(n=>{const s=n.dataset.value===(e.value||"");n.classList.toggle("selected",s),n.setAttribute("aria-checked",s?"true":"false")})},_filterSettings(e){const t=document.getElementById("appSettingsDoc");if(!t)return;const n=(e||"").trim().toLowerCase(),s=".set-row, .set-chip, .set-modelgrid, .set-minigrid, .event-type-grid, #appSettingsShortcutsList",o=[...t.querySelectorAll(s)];let i=!1;o.forEach(l=>{if(!n){l.classList.remove("set-hit-hidden");return}const d=`${l.dataset?.search||""} ${l.textContent||""}`.toLowerCase().includes(n);l.classList.toggle("set-hit-hidden",!d),d&&(i=!0)}),t.querySelectorAll(".set-chips").forEach(l=>{const c=[...l.querySelectorAll(".set-chip")].some(d=>!d.classList.contains("set-hit-hidden"));l.classList.toggle("set-hit-hidden",!!n&&!c)}),t.querySelectorAll(".set-group").forEach(l=>{const c=[...l.querySelectorAll(s)].some(d=>!d.classList.contains("set-hit-hidden"));l.classList.toggle("set-hit-hidden",!!n&&!c),n&&c&&l.classList.add("open")}),t.querySelectorAll(".set-section").forEach(l=>{const c=[...l.querySelectorAll(".set-group")].some(d=>!d.classList.contains("set-hit-hidden"));l.classList.toggle("set-hit-hidden",!!n&&!c)}),t.querySelectorAll(".set-preview").forEach(l=>l.classList.toggle("set-hit-hidden",!!n));const a=document.getElementById("appSettingsSearchEmpty");a&&(a.hidden=!n||i),n||t.querySelectorAll(".set-group-advanced").forEach(l=>l.classList.remove("open"))},closeAppSettings(){this._toggleSettingsJump(!1),document.getElementById("appSettingsModal").classList.remove("active"),this.activeFocusTrap&&(this.activeFocusTrap.deactivate(),this.activeFocusTrap=null)},_updatePhaseText(e){return{queued:"Queued\u2026",preparing:"Preparing\u2026",stashing:"Stashing local changes\u2026",fetching:"Fetching release\u2026",checkout:"Checking out release\u2026",installing:"Installing dependencies\u2026",building:"Building\u2026",restarting:"Restarting Codeman\u2026"}[e]||e},_initUpdatesSection(){const e=this.$("updateCurrentVersion");e&&(e.textContent=(this.$("versionDisplay")?.textContent||"").trim()||"\u2014");for(const t of["updateResult","updateActionRow","updateNotes","updateProgress"]){const n=this.$(t);n&&(n.style.display="none")}this._updateCheck=null},_setUpdateResult(e){const t=this.$("updateResult");t&&(t.style.display="block",t.innerHTML=e)},_setUpdateProgress(e){const t=this.$("updateProgress");t&&(t.style.display="block",t.innerHTML=e)},async checkForUpdate(){const e=this.$("updateCheckBtn");e&&(e.disabled=!0,e.textContent="Checking\u2026");const t=await this._apiJson("/api/system/update/check");e&&(e.disabled=!1,e.textContent="Check now");const n=this.$("updateActionRow"),s=this.$("updateNotes");if(n&&(n.style.display="none"),s&&(s.style.display="none"),!t){this._setUpdateResult("Could not check for updates. Try again later.");return}this._updateCheck=t;const o=this.$("updateCurrentVersion");if(o&&t.currentVersion&&(o.textContent=`v${t.currentVersion}`),t.installKind&&t.installKind!=="git"){this._setUpdateResult(`This install can't update itself (${escapeHtml(t.installKind)}). Update with <code>npm i -g aicodeman@latest</code>.`);return}if(t.selfUpdateEnabled===!1){this._setUpdateResult("In-app updates are disabled on this server (CODEMAN_DISABLE_SELF_UPDATE=1).");return}if(t.error&&!t.updateAvailable){this._setUpdateResult(escapeHtml(t.error));return}if(t.updateAvailable&&t.latestVersion){this._setUpdateResult(`Update available: <strong>v${escapeHtml(t.latestVersion)}</strong> (current v${escapeHtml(t.currentVersion||"")})`);const i=this.$("updateActionLabel");i&&(i.textContent=`Update to v${t.latestVersion}`),n&&(n.style.display="flex");const a=this.$("updateNowBtn");a&&(a.disabled=!1,a.textContent="Update now"),s&&t.notes&&(s.style.display="block",s.textContent=t.notes)}else this._setUpdateResult(`You're up to date (v${escapeHtml(t.currentVersion||"")}).`)},async startSelfUpdate(){const e=this._updateCheck?.latestVersion?`v${this._updateCheck.latestVersion}`:"the latest release";if(!confirm(`Update Codeman to ${e}? The server will restart and this page will reload.`))return;const t=this.$("updateNowBtn");t&&(t.disabled=!0,t.textContent="Starting\u2026");const n=await this._apiPost("/api/system/update",{});if(!n||!n.ok){let i="Failed to start the update.";try{const a=await n.json();typeof a?.error=="string"&&a.error&&(i=a.error)}catch{}this._setUpdateProgress(`<span style="color:var(--danger,#e5534b)">${escapeHtml(i)}</span>`),t&&(t.disabled=!1,t.textContent="Update now");return}const s=this.$("updateActionRow");s&&(s.style.display="none");const o=this.$("updateNotes");o&&(o.style.display="none"),this._setUpdateProgress("Starting update\u2026"),this._pollUpdateStatus()},_stopUpdatePolling(){this._updatePollTimer&&(clearInterval(this._updatePollTimer),this._updatePollTimer=null)},_pollUpdateStatus(){this._stopUpdatePolling();const e=new Set(["completed","completed-needs-manual-restart","failed","idle"]),t=async()=>{let n=null;try{const s=await fetch("/api/system/update/status");if(s.ok){const o=await s.json();n=o&&o.success===!0?o.data:o}}catch{}if(!n){this._setUpdateProgress("\u21BB Restarting Codeman\u2026");return}if(!e.has(n.phase)){const s=n.message&&n.message.trim()?n.message.trim():this._updatePhaseText(n.phase);let o="";n.startedAt&&(o=` <span style="color:var(--text-secondary)">\xB7 ${Math.max(0,Math.round((Date.now()-n.startedAt)/1e3))}s</span>`),this._setUpdateProgress(`<span class="tunnel-spinner"></span> ${escapeHtml(s)}${o}`);return}if(this._stopUpdatePolling(),n.phase==="completed"){let s=`<span style="color:var(--success,#3fb950)">\u2713 Updated to v${escapeHtml(n.toVersion||"")}. Reloading\u2026</span>`;n.stashRef&&(s+=`<br><span style="color:var(--text-secondary)">Local changes stashed as <code>${escapeHtml(n.stashRef)}</code> \u2014 run <code>git stash pop</code> to restore.</span>`),this._setUpdateProgress(s),setTimeout(()=>location.reload(),2500)}else if(n.phase==="completed-needs-manual-restart")this._setUpdateProgress(`Update staged. Restart Codeman to apply:<br><code>${escapeHtml(n.manualRestartCommand||"restart codeman web")}</code>`);else if(n.phase==="failed"){let s=`<span style="color:var(--danger,#e5534b)">\u2717 ${escapeHtml(n.message||"Update failed")}.</span>`;n.error&&(s+=`<br><span style="color:var(--text-secondary)">${escapeHtml(n.error)}</span>`),s+='<br><span style="color:var(--text-secondary)">The previous version is still running.</span>',n.stashRef&&(s+=`<br><span style="color:var(--text-secondary)">Local changes stashed as <code>${escapeHtml(n.stashRef)}</code>.</span>`),this._setUpdateProgress(s);const o=this.$("updateNowBtn"),i=this.$("updateActionRow");o&&(o.disabled=!1,o.textContent="Try again"),i&&(i.style.display="flex")}};t(),this._updatePollTimer=setInterval(t,1500)},isCliAvailable(e){const t=window.__codemanCliAvailable;return!t||typeof t!="object"?!0:t[e]!==!1},applyWelcomeCliVisibility(){const e=[["welcomeClaudeBtn","claude"],["welcomeOpencodeBtn","opencode"],["welcomeAntigravityBtn","antigravity"],["welcomeGeminiBtn","gemini"],["welcomeTunnelBtn","cloudflared"]];for(const[t,n]of e){const s=document.getElementById(t);s&&(s.style.display=this.isCliAvailable(n)?"flex":"none")}},async loadTunnelStatus(){try{const t=await(await fetch("/api/tunnel/status")).json(),n=t?.success===!0?t.data:t,s=n.running&&n.url;this._tunnelUrl=s?n.url:null,this._updateTunnelUrlDisplay(this._tunnelUrl),this._updateWelcomeTunnelBtn(!!s,this._tunnelUrl),this._updateTunnelIndicator(!!s)}catch{this._tunnelUrl=null,this._updateTunnelUrlDisplay(null),this._updateWelcomeTunnelBtn(!1),this._updateTunnelIndicator(!1)}},_updateTunnelUrlRow(e,t,n,s=""){const o=document.getElementById(e),i=document.getElementById(t);if(!(!o||!i))if(n){const a=n+s;o.style.display="",i.textContent=a,i.onclick=()=>{navigator.clipboard.writeText(a).then(()=>{this.showToast(`${s?"Upload":"Tunnel"} URL copied`,"success")})}}else o.style.display="none",i.textContent="",i.onclick=null},_updateTunnelUrlDisplay(e){this._updateTunnelUrlRow("tunnelUrlRow","tunnelUrlDisplay",e),this._updateTunnelUrlRow("tunnelUploadUrlRow","tunnelUploadUrlDisplay",e,"/upload.html")},showTunnelQR(){this.closeTunnelQR();const e=document.createElement("div");e.id="tunnelQrOverlay",e.style.cssText="position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:5000;display:flex;align-items:center;justify-content:center;cursor:pointer",e.onclick=n=>{n.target===e&&this.closeTunnelQR()};const t=document.createElement("div");t.style.cssText="background:var(--bg-card);border:1px solid var(--border);border-radius:12px;padding:24px;text-align:center;max-width:340px;width:90vw;box-shadow:var(--shadow-lg);cursor:default",t.innerHTML=`
|
|
2
|
+
<div style="font-size:14px;font-weight:600;color:var(--text-primary);margin-bottom:16px">Scan to connect</div>
|
|
3
|
+
<div id="tunnelQrContainer" style="background:#fff;border-radius:8px;padding:16px;display:inline-block">
|
|
4
|
+
<div style="color:#666;font-size:12px">Loading...</div>
|
|
5
|
+
</div>
|
|
6
|
+
<div id="tunnelQrUrl" style="margin-top:12px;font-family:monospace;font-size:11px;color:var(--text-muted);word-break:break-all;cursor:pointer" title="Click to copy"></div>
|
|
7
|
+
<button onclick="app.closeTunnelQR()" style="margin-top:16px;padding:6px 20px;background:var(--bg-elevated);border:1px solid var(--border);border-radius:6px;color:var(--text-primary);cursor:pointer;font-size:13px">Close</button>
|
|
8
|
+
`,e.appendChild(t),document.body.appendChild(e),fetch("/api/tunnel/qr").then(n=>{if(!n.ok)throw new Error("Tunnel not running");return n.json()}).then(n=>{const s=n?.success===!0?n.data:n,o=document.getElementById("tunnelQrContainer");if(o&&s.svg&&(o.innerHTML=s.svg),s.authEnabled){const i=document.createElement("div");i.id="tunnelQrBadge",i.style.cssText="margin-top:8px;font-size:11px;color:var(--text-muted)",i.textContent="Single-use auth \xB7 expires in 60s";const a=document.createElement("button");a.textContent="Regenerate QR",a.style.cssText="margin-top:8px;padding:4px 12px;background:var(--bg-elevated);border:1px solid var(--border);border-radius:4px;color:var(--text-secondary);cursor:pointer;font-size:11px",a.onclick=()=>{fetch("/api/tunnel/qr/regenerate",{method:"POST"}).then(()=>this.showToast("QR code regenerated","success")).catch(()=>this.showToast("Failed to regenerate QR","error"))};const l=o.parentElement;l&&(l.appendChild(i),l.appendChild(a)),this._resetQrCountdown()}}).catch(()=>{const n=document.getElementById("tunnelQrContainer");n&&(n.innerHTML='<div style="color:#c00;font-size:12px;padding:20px">Tunnel not active</div>')}),fetch("/api/tunnel/status").then(n=>n.json()).then(n=>{const s=n?.success===!0?n.data:n,o=document.getElementById("tunnelQrUrl");o&&s.url&&(o.textContent=s.url,o.onclick=()=>{navigator.clipboard.writeText(s.url).then(()=>{this.showToast("Tunnel URL copied","success")})})}).catch(()=>{}),this._tunnelQrEscHandler=n=>{n.key==="Escape"&&this.closeTunnelQR()},document.addEventListener("keydown",this._tunnelQrEscHandler)},closeTunnelQR(){const e=document.getElementById("tunnelQrOverlay");e&&e.remove(),this._tunnelQrEscHandler&&(document.removeEventListener("keydown",this._tunnelQrEscHandler),this._tunnelQrEscHandler=null),this._clearQrCountdown()},_refreshTunnelQrFromApi(){fetch("/api/tunnel/qr").then(e=>e.ok?e.json():null).then(e=>{const t=e?.success===!0?e.data:e;if(!t?.svg)return;const n=document.getElementById("tunnelQrContainer");n&&(n.innerHTML=t.svg);const s=document.getElementById("welcomeQrInner");s&&(s.innerHTML=t.svg)}).catch(()=>{})},_resetQrCountdown(){this._clearQrCountdown(),this._qrCountdownSec=60,this._updateQrCountdownText(),this._qrCountdownTimer=setInterval(()=>{if(this._qrCountdownSec--,this._qrCountdownSec<=0){this._clearQrCountdown();return}this._updateQrCountdownText()},1e3)},_updateQrCountdownText(){const e=document.getElementById("tunnelQrBadge");e&&(e.textContent=`Single-use auth \xB7 expires in ${this._qrCountdownSec}s`)},_clearQrCountdown(){this._qrCountdownTimer&&(clearInterval(this._qrCountdownTimer),this._qrCountdownTimer=null)},async toggleTunnelFromWelcome(){const e=document.getElementById("welcomeTunnelBtn");if(!e)return;const t=e.classList.contains("active");e.disabled=!0;try{const n=!t,s=await fetch("/api/settings",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({tunnelEnabled:n})});if(n&&await this._handleTunnelEnableRefusal(s)){this._dismissTunnelConnecting(),this._updateWelcomeTunnelBtn(!1),e.disabled=!1;return}n?(this._showTunnelConnecting(),this._pollTunnelStatus()):(this._dismissTunnelConnecting(),this.showToast("Tunnel stopped","info"),this._updateWelcomeTunnelBtn(!1),e.disabled=!1)}catch{this._dismissTunnelConnecting(),this.showToast("Failed to toggle tunnel","error"),e.disabled=!1}},_showTunnelConnecting(){const e=document.getElementById("tunnelConnectingToast");e&&e.remove();const t=document.getElementById("welcomeTunnelBtn");t&&(t.classList.add("connecting"),t.innerHTML=`
|
|
9
|
+
<span class="tunnel-spinner"></span>
|
|
10
|
+
Connecting...`);const n=document.createElement("div");n.className="toast toast-info show",n.id="tunnelConnectingToast",n.innerHTML='<span class="tunnel-spinner"></span> Cloudflare Tunnel connecting...',n.style.pointerEvents="auto",this._toastContainer||(this._toastContainer=document.querySelector(".toast-container"),this._toastContainer||(this._toastContainer=document.createElement("div"),this._toastContainer.className="toast-container",document.body.appendChild(this._toastContainer))),this._toastContainer.appendChild(n)},_dismissTunnelConnecting(){clearTimeout(this._tunnelPollTimer),this._tunnelPollTimer=null;const e=document.getElementById("tunnelConnectingToast");e&&(e.classList.remove("show"),setTimeout(()=>e.remove(),200));const t=document.getElementById("welcomeTunnelBtn");t&&t.classList.remove("connecting")},_pollTunnelStatus(e=0){e>15||(this._tunnelPollTimer=setTimeout(async()=>{try{const n=await(await fetch("/api/tunnel/status")).json(),s=n?.success===!0?n.data:n;if(s.running&&s.url){this._dismissTunnelConnecting(),this._updateTunnelUrlDisplay(s.url),document.getElementById("welcomeOverlay")?.classList.contains("visible")?(this._updateWelcomeTunnelBtn(!0,s.url,!0),this.showToast("Tunnel active","success")):(this._updateWelcomeTunnelBtn(!0,s.url),this.showToast(`Tunnel active: ${s.url}`,"success"),this.showTunnelQR());return}}catch{}this._pollTunnelStatus(e+1)},2e3))},_updateWelcomeTunnelBtn(e,t,n=!1){const s=document.getElementById("welcomeTunnelBtn");s&&(s.disabled=!1,e?(s.classList.remove("connecting"),s.classList.add("active"),s.innerHTML=`
|
|
11
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
|
|
12
|
+
Tunnel Active`):(s.classList.remove("active","connecting"),s.innerHTML=`
|
|
13
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
|
|
14
|
+
Cloudflare Tunnel`));const o=document.getElementById("welcomeQr"),i=document.getElementById("welcomeQrInner"),a=document.getElementById("welcomeQrUrl");!o||!i||(e?(o.classList.add("visible"),n&&(o.classList.add("expanded"),clearTimeout(this._welcomeQrShrinkTimer),this._welcomeQrShrinkTimer=setTimeout(()=>{o.classList.remove("expanded")},8e3)),t&&(a.textContent=t,a.title="Click QR to enlarge"),fetch("/api/tunnel/qr").then(l=>{if(!l.ok)throw new Error;return l.json()}).then(l=>{const c=l?.success===!0?l.data:l;c.svg&&(i.innerHTML=c.svg)}).catch(()=>{i.innerHTML='<div style="color:#999;font-size:11px;padding:20px">QR unavailable</div>'})):(clearTimeout(this._welcomeQrShrinkTimer),o.classList.remove("visible","expanded"),i.innerHTML="",a&&(a.textContent="")))},toggleWelcomeQrSize(){const e=document.getElementById("welcomeQr");e&&(clearTimeout(this._welcomeQrShrinkTimer),e.classList.toggle("expanded"))},_updateTunnelIndicator(e){if(MobileDetection.getDeviceType()==="mobile")return;const t=document.getElementById("tunnelIndicator");t&&(t.style.display=e?"flex":"none",t.classList.remove("connecting"))},toggleTunnelPanel(){if(document.getElementById("tunnelPanel")){this.closeTunnelPanel();return}this._openTunnelPanel()},async _openTunnelPanel(){const e=document.createElement("div");e.className="tunnel-panel",e.id="tunnelPanel",e.innerHTML=`
|
|
15
|
+
<div class="tunnel-panel-header">
|
|
16
|
+
<h3>
|
|
17
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
|
|
18
|
+
Cloudflare Tunnel
|
|
19
|
+
<span class="tunnel-panel-status" id="tunnelPanelStatus">Loading...</span>
|
|
20
|
+
</h3>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="tunnel-panel-body" id="tunnelPanelBody">
|
|
23
|
+
<div style="font-size:12px;color:var(--text-muted);padding:8px 0">Loading...</div>
|
|
24
|
+
</div>
|
|
25
|
+
`,document.body.appendChild(e),this._tunnelPanelClickHandler=t=>{!e.contains(t.target)&&t.target.id!=="tunnelIndicator"&&!t.target.closest(".tunnel-indicator")&&this.closeTunnelPanel()},setTimeout(()=>document.addEventListener("click",this._tunnelPanelClickHandler),0),this._tunnelPanelEscHandler=t=>{t.key==="Escape"&&this.closeTunnelPanel()},document.addEventListener("keydown",this._tunnelPanelEscHandler);try{const n=await(await fetch("/api/tunnel/info")).json(),s=n?.success===!0?n.data:n;this._renderTunnelPanel(s)}catch{const t=document.getElementById("tunnelPanelBody");t&&(t.innerHTML='<div style="font-size:12px;color:var(--red);padding:8px 0">Failed to load tunnel info</div>')}},_renderTunnelPanel(e){const t=document.getElementById("tunnelPanelStatus"),n=document.getElementById("tunnelPanelBody");if(!t||!n)return;t.textContent=e.running?"Connected":"Offline",t.className="tunnel-panel-status"+(e.running?"":" offline");let s="";if(e.url&&(s+=`
|
|
26
|
+
<div class="tunnel-panel-section">
|
|
27
|
+
<div class="tunnel-panel-label">URL</div>
|
|
28
|
+
<div class="tunnel-panel-url" id="tunnelPanelUrl" title="Click to copy">${escapeHtml(e.url)}</div>
|
|
29
|
+
</div>`),s+=`
|
|
30
|
+
<div class="tunnel-panel-section">
|
|
31
|
+
<div class="tunnel-panel-label">Connections</div>
|
|
32
|
+
<div class="tunnel-panel-stat">
|
|
33
|
+
<span>Remote Clients</span>
|
|
34
|
+
<span class="tunnel-panel-stat-value">${e.sseClients}</span>
|
|
35
|
+
</div>`,e.authEnabled&&(s+=`
|
|
36
|
+
<div class="tunnel-panel-stat">
|
|
37
|
+
<span>Auth Sessions</span>
|
|
38
|
+
<span class="tunnel-panel-stat-value">${e.authSessions.length}</span>
|
|
39
|
+
</div>`),s+="</div>",e.authEnabled&&e.authSessions.length>0){s+='<div class="tunnel-panel-section"><div class="tunnel-panel-label">Authenticated Devices</div>';for(const i of e.authSessions){const a=i.ua||"Unknown",l=a.match(/Chrome|Firefox|Safari|Edge|Mobile/)?.[0]||"Browser",c=this._formatTimeAgo(i.createdAt);s+=`
|
|
40
|
+
<div class="tunnel-panel-session">
|
|
41
|
+
<span class="tunnel-panel-session-dot"></span>
|
|
42
|
+
<span class="tunnel-panel-session-info" title="${escapeHtml(a)}">${escapeHtml(l)} · ${escapeHtml(i.ip)} · ${c}</span>
|
|
43
|
+
<span class="tunnel-panel-session-method">${i.method}</span>
|
|
44
|
+
</div>`}s+="</div>"}s+='<div class="tunnel-panel-actions">',e.running?s+=`
|
|
45
|
+
<button class="tunnel-panel-btn btn-qr" onclick="app.showTunnelQR();app.closeTunnelPanel()">QR Code</button>
|
|
46
|
+
<button class="tunnel-panel-btn btn-stop" onclick="app._tunnelPanelToggle(false)">Stop Tunnel</button>`:s+='<button class="tunnel-panel-btn btn-start" onclick="app._tunnelPanelToggle(true)">Start Tunnel</button>',s+="</div>",e.authEnabled&&e.authSessions.length>0&&(s+=`
|
|
47
|
+
<div style="padding-top:8px">
|
|
48
|
+
<button class="tunnel-panel-btn btn-revoke" style="width:100%" onclick="app._tunnelPanelRevokeAll()">Revoke All Sessions</button>
|
|
49
|
+
</div>`),n.innerHTML=s;const o=document.getElementById("tunnelPanelUrl");o&&(o.onclick=()=>{navigator.clipboard.writeText(e.url).then(()=>this.showToast("Tunnel URL copied","success"))})},_formatTimeAgo(e){const t=Date.now()-e,n=Math.floor(t/6e4);if(n<1)return"just now";if(n<60)return`${n}m ago`;const s=Math.floor(n/60);return s<24?`${s}h ago`:`${Math.floor(s/24)}d ago`},async _handleTunnelEnableRefusal(e){if(!e||e.ok)return!1;let t="Tunnel refused: set CODEMAN_PASSWORD before exposing Codeman publicly.";try{const n=await e.json();n&&n.error&&(t=n.error)}catch{}if(e.status===403){if(!confirm(`\u26A0\uFE0F SECURITY WARNING \u2014 no password set
|
|
50
|
+
|
|
51
|
+
Enabling the Cloudflare tunnel will publish THIS machine to a public URL with NO login. Anyone who gets the URL has full terminal control \u2014 effectively remote code execution on your computer.
|
|
52
|
+
|
|
53
|
+
Strongly recommended: set CODEMAN_PASSWORD instead.
|
|
54
|
+
|
|
55
|
+
Enable the unauthenticated public tunnel anyway?`))return this._dismissTunnelConnecting?.(),this.showToast("Tunnel not enabled","info"),!0;try{const s=await fetch("/api/settings",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({tunnelEnabled:!0,acknowledgeUnauthTunnel:!0})});if(s.ok)return this.showToast("Public tunnel enabling \u2014 no password set \u26A0\uFE0F","warning"),!1;let o="Failed to enable tunnel.";try{const i=await s.json();i&&i.error&&(o=i.error)}catch{}return this._dismissTunnelConnecting?.(),this.showToast(o,"error"),!0}catch{return this._dismissTunnelConnecting?.(),this.showToast("Failed to enable tunnel","error"),!0}}return this._dismissTunnelConnecting?.(),this.showToast(t,"error"),!0},async _tunnelPanelToggle(e){try{const t=await fetch("/api/settings",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({tunnelEnabled:e})});if(e&&await this._handleTunnelEnableRefusal(t)){this.closeTunnelPanel();return}if(e){this._updateTunnelIndicator(!1);const n=document.getElementById("tunnelIndicator");n&&(n.style.display="flex",n.classList.add("connecting")),this.showToast("Tunnel starting...","info"),this._showTunnelConnecting(),this._pollTunnelStatus()}else this.showToast("Tunnel stopped","info");this.closeTunnelPanel()}catch{this.showToast("Failed to toggle tunnel","error")}},async _tunnelPanelRevokeAll(){try{await fetch("/api/auth/revoke",{method:"POST",headers:{"Content-Type":"application/json"},body:"{}"}),this.showToast("All sessions revoked","success");const t=await(await fetch("/api/tunnel/info")).json(),n=t?.success===!0?t.data:t;this._renderTunnelPanel(n)}catch{this.showToast("Failed to revoke sessions","error")}},closeTunnelPanel(){const e=document.getElementById("tunnelPanel");e&&e.remove(),this._tunnelPanelClickHandler&&(document.removeEventListener("click",this._tunnelPanelClickHandler),this._tunnelPanelClickHandler=null),this._tunnelPanelEscHandler&&(document.removeEventListener("keydown",this._tunnelPanelEscHandler),this._tunnelPanelEscHandler=null)},toggleDeepgramKeyVisibility(){const e=document.getElementById("voiceDeepgramKey"),t=document.getElementById("voiceKeyToggleBtn");e.type==="password"?(e.type="text",t.textContent="Hide"):(e.type="password",t.textContent="Show")},openLifecycleLog(){const e=document.getElementById("lifecycleWindow");e.style.display="block",e._dragInitialized||(e.style.left="50%",e.style.transform="translateX(-50%)",this._initLifecycleDrag(e),e._dragInitialized=!0),this.loadLifecycleLog()},closeLifecycleLog(){document.getElementById("lifecycleWindow").style.display="none"},_initLifecycleDrag(e){const t=document.getElementById("lifecycleWindowHeader");let n=!1,s,o,i,a;t.addEventListener("mousedown",l=>{if(l.target.tagName==="SELECT"||l.target.tagName==="INPUT"||l.target.tagName==="BUTTON")return;n=!0;const c=e.getBoundingClientRect();e.style.transform="none",e.style.left=c.left+"px",e.style.top=c.top+"px",s=l.clientX,o=l.clientY,i=c.left,a=c.top,l.preventDefault()}),document.addEventListener("mousemove",l=>{n&&(e.style.left=i+l.clientX-s+"px",e.style.top=a+l.clientY-o+"px")}),document.addEventListener("mouseup",()=>{n=!1})},async loadLifecycleLog(){const e=document.getElementById("lifecycleFilterEvent").value,t=document.getElementById("lifecycleFilterSession").value.trim(),n=new URLSearchParams;e&&n.set("event",e),t&&n.set("sessionId",t),n.set("limit","300");try{const o=await(await fetch(`/api/session-lifecycle?${n}`)).json(),i=o?.success===!0?o.data:o,a=document.getElementById("lifecycleTableBody"),l=document.getElementById("lifecycleEmpty");if(!i.entries||i.entries.length===0){a.innerHTML="",l.style.display="";return}l.style.display="none";const c={created:"#4ade80",started:"#4ade80",recovered:"#4ade80",exit:"#fbbf24",mux_died:"#f87171",deleted:"#f87171",stale_cleaned:"#f87171",server_started:"#666",server_stopped:"#666"};a.innerHTML=i.entries.map(d=>{const r=new Date(d.ts).toLocaleString(),u=c[d.event]||"#888",g=d.name||(d.sessionId==="*"?"\u2014":this.getShortId(d.sessionId)),p=[];return d.exitCode!==void 0&&d.exitCode!==null&&p.push(`code=${d.exitCode}`),d.mode&&p.push(d.mode),`<tr style="border-bottom:1px solid #1a1a2e">
|
|
56
|
+
<td style="padding:3px 8px;color:#888;white-space:nowrap">${r}</td>
|
|
57
|
+
<td style="padding:3px 8px;color:${u};font-weight:600">${d.event}</td>
|
|
58
|
+
<td style="padding:3px 8px;color:#e0e0e0" title="${d.sessionId}">${g}</td>
|
|
59
|
+
<td style="padding:3px 8px;color:#aaa">${d.reason||""}</td>
|
|
60
|
+
<td style="padding:3px 8px;color:#666">${p.join(", ")}</td>
|
|
61
|
+
</tr>`}).join("")}catch(s){console.error("Failed to load lifecycle log:",s)}},_renderVoiceProviderStatus(){const e=document.getElementById("voiceProviderStatus");if(e){const o=VoiceInput.getActiveProviderName();e.textContent=o;const i=o.startsWith("Deepgram Nova")||o.startsWith("Claude (this");e.className="voice-provider-status"+(i?" active":"")}const t=document.getElementById("voiceClaudeStatus");if(!t)return;const n=VoiceInput._claudeStatus,s=n?n.available?`Ready${n.subscriptionType?` (${n.subscriptionType})`:""}`:n.reason==="expired"?"Login expired - run a Claude session to refresh":n.reason==="no-credentials"?"No Claude Code login on the server":n.reason==="malformed"?"Claude credentials unreadable":"Off - enable it above":"Checking...";t.textContent=s,t.className="voice-provider-status"+(n?.available?" active":"")},async saveAppSettings(){const e=this.loadAppSettingsFromStorage(),t=(e.gestureControlEnabled??!1)===!0,n=(e.webglRendererEnabled??!0)===!0,s={displayName:window.CodemanI18n?.normalizeDisplayName(document.getElementById("appSettingsDisplayName").value)||"Codeman",language:window.CodemanI18n?.normalizeLanguage(document.getElementById("appSettingsLanguage").value)||"en",defaultClaudeMdPath:document.getElementById("appSettingsClaudeMdPath").value.trim(),defaultWorkingDir:document.getElementById("appSettingsDefaultDir").value.trim(),ralphTrackerEnabled:document.getElementById("appSettingsRalphEnabled").checked,showFontControls:document.getElementById("appSettingsShowFontControls").checked,showSystemStats:document.getElementById("appSettingsShowSystemStats").checked,showLifecycleLog:document.getElementById("appSettingsShowLifecycleLog").checked,showResponseViewer:document.getElementById("appSettingsShowResponseViewer").checked,showFileViewerButton:document.getElementById("appSettingsShowFileViewerButton").checked,showAttachmentsButton:document.getElementById("appSettingsShowAttachmentsButton").checked,showMonitor:document.getElementById("appSettingsShowMonitor").checked,showProjectInsights:document.getElementById("appSettingsShowProjectInsights").checked,showFileBrowser:document.getElementById("appSettingsShowFileBrowser").checked,showSubagents:document.getElementById("appSettingsShowSubagents").checked,showUltracodeAgents:document.getElementById("appSettingsShowUltracodeAgents").checked,approvalsInboxEnabled:document.getElementById("appSettingsApprovalsInbox").checked,readMyMindEnabled:document.getElementById("appSettingsReadMyMind").checked,ultracodeFloatingWindows:document.getElementById("appSettingsUltracodeFloatingWindows").checked,showMultiMonitorButton:document.getElementById("appSettingsShowMultiMonitorButton").checked,showPlanUsageLimits:document.getElementById("appSettingsShowPlanUsageLimits").checked,showRedrawButton:document.getElementById("appSettingsShowRedrawButton").checked,mobileOverviewEnabled:document.getElementById("appSettingsMobileOverview").checked,sessionLineageLines:document.getElementById("appSettingsLineageLines").checked,showSessionButton:document.getElementById("appSettingsShowSessionButton").checked,showAwayDigestButton:document.getElementById("appSettingsShowAwayDigestButton").checked,showCronButton:document.getElementById("appSettingsShowCronButton").checked,gestureControlEnabled:document.getElementById("appSettingsGestureControl").checked,subagentTrackingEnabled:document.getElementById("appSettingsSubagentTracking").checked,subagentActiveTabOnly:document.getElementById("appSettingsSubagentActiveTabOnly").checked,imageWatcherEnabled:document.getElementById("appSettingsImageWatcherEnabled").checked,tunnelEnabled:document.getElementById("appSettingsTunnelEnabled").checked,localEchoEnabled:document.getElementById("appSettingsLocalEcho").checked,terminalWheelLocalScrollback:document.getElementById("appSettingsTerminalWheelLocal").checked,cjkInputEnabled:document.getElementById("appSettingsCjkInput").checked,webglRendererEnabled:document.getElementById("appSettingsWebglRenderer").checked,extendedKeyboardBar:document.getElementById("appSettingsExtendedKeyboardBar").checked,tabTwoRows:document.getElementById("appSettingsTabTwoRows").checked,showTabDetachButton:document.getElementById("appSettingsShowTabDetachButton").checked,skin:document.getElementById("appSettingsSkin").value,claudeMode:document.getElementById("appSettingsClaudeMode").value,allowedTools:document.getElementById("appSettingsAllowedTools").value.trim(),codexDangerouslyBypassApprovals:document.getElementById("appSettingsCodexDangerouslyBypassApprovals").checked,codexAnimationsEnabled:document.getElementById("appSettingsCodexAnimations").checked,agentTeamsEnabled:document.getElementById("appSettingsAgentTeams").checked,agentSkillEnabled:document.getElementById("appSettingsAgentSkill").checked,claudeVoiceEnabled:document.getElementById("appSettingsClaudeVoice").checked,claudeModel:document.getElementById("appSettingsClaudeModel").value,opusContext1mEnabled:document.getElementById("appSettingsOpusContext1m").checked,remoteAutoReconnect:document.getElementById("appSettingsRemoteAutoReconnect").checked,thinkingEffort:document.getElementById("appSettingsThinkingEffort").value,nice:{enabled:document.getElementById("appSettingsNiceEnabled").checked,niceValue:parseInt(document.getElementById("appSettingsNiceValue").value)||10}};if(e.showTokenCount!==void 0&&(s.showTokenCount=e.showTokenCount),e.showCost!==void 0&&(s.showCost=e.showCost),e.shortcutOverrides!==void 0&&(s.shortcutOverrides=e.shortcutOverrides),this.saveAppSettingsToStorage(s),this._updateLocalEchoState(),!n&&s.webglRendererEnabled)try{localStorage.removeItem("codeman-webgl-disabled")}catch{}const o={provider:document.getElementById("voiceProvider").value,apiKey:document.getElementById("voiceDeepgramKey").value.trim(),language:document.getElementById("voiceLanguage").value,keyterms:document.getElementById("voiceKeyterms").value.trim(),insertMode:document.getElementById("voiceInsertMode").value};VoiceInput._saveDeepgramConfig(o);const i={enabled:document.getElementById("appSettingsNotifEnabled").checked,browserNotifications:document.getElementById("appSettingsNotifBrowser").checked,audioAlerts:document.getElementById("appSettingsNotifAudio").checked,stuckThresholdMs:(parseInt(document.getElementById("appSettingsNotifStuckMins").value)||10)*6e4,muteCritical:!document.getElementById("appSettingsNotifCritical").checked,muteWarning:!document.getElementById("appSettingsNotifWarning").checked,muteInfo:!document.getElementById("appSettingsNotifInfo").checked,eventTypes:{permission_prompt:{enabled:document.getElementById("eventPermissionEnabled").checked,browser:document.getElementById("eventPermissionBrowser").checked,push:document.getElementById("eventPermissionPush").checked,audio:document.getElementById("eventPermissionAudio").checked},elicitation_dialog:{enabled:document.getElementById("eventQuestionEnabled").checked,browser:document.getElementById("eventQuestionBrowser").checked,push:document.getElementById("eventQuestionPush").checked,audio:document.getElementById("eventQuestionAudio").checked},idle_prompt:{enabled:document.getElementById("eventIdleEnabled").checked,browser:document.getElementById("eventIdleBrowser").checked,push:document.getElementById("eventIdlePush").checked,audio:document.getElementById("eventIdleAudio").checked},stop:{enabled:document.getElementById("eventStopEnabled").checked,browser:document.getElementById("eventStopBrowser").checked,push:document.getElementById("eventStopPush").checked,audio:document.getElementById("eventStopAudio").checked},session_error:{enabled:!0,browser:this.notificationManager?.preferences?.eventTypes?.session_error?.browser??!0,push:this.notificationManager?.preferences?.eventTypes?.session_error?.push??!1,audio:!1},respawn_cycle:{enabled:document.getElementById("eventRespawnEnabled").checked,browser:document.getElementById("eventRespawnBrowser").checked,push:document.getElementById("eventRespawnPush").checked,audio:document.getElementById("eventRespawnAudio").checked},token_milestone:{enabled:!0,browser:!1,push:!1,audio:!1},ralph_complete:{enabled:document.getElementById("eventRalphEnabled").checked,browser:document.getElementById("eventRalphBrowser").checked,push:document.getElementById("eventRalphPush").checked,audio:document.getElementById("eventRalphAudio").checked},subagent_spawn:{enabled:document.getElementById("eventSubagentEnabled").checked,browser:document.getElementById("eventSubagentBrowser").checked,push:document.getElementById("eventSubagentPush").checked,audio:document.getElementById("eventSubagentAudio").checked},subagent_complete:{enabled:document.getElementById("eventSubagentEnabled").checked,browser:document.getElementById("eventSubagentBrowser").checked,push:document.getElementById("eventSubagentPush").checked,audio:document.getElementById("eventSubagentAudio").checked}},_version:5};this.notificationManager&&(this.notificationManager.preferences=i,this.notificationManager.savePreferences()),this._syncPushPreferences(),this.applyHeaderVisibilitySettings(),this.applySkin(),this.applyLocalization(),this.applyTabWrapSettings(),this.applyLineageLineSettings?.(),this._updateTokensImmediate(),this.applyMonitorVisibility(),this.renderApprovals?.(),this.renderProjectInsightsPanel(),this.updateSubagentWindowVisibility(),this._updateCjkInputState(),this.activeSessionId||this.showWelcome(),KeyboardAccessoryBar.setMode(s.extendedKeyboardBar?"extended":"simple");const{localEchoEnabled:a,cjkInputEnabled:l,extendedKeyboardBar:c,skin:d,language:r,showPlanUsageLimits:u,showAttachmentsButton:g,showFileViewerButton:p,webglRendererEnabled:S,terminalWheelLocalScrollback:y,showSessionButton:b,showAwayDigestButton:f,showCronButton:w,showTabDetachButton:h,mobileOverviewEnabled:B,sessionLineageLines:v,...m}=s;try{const I=await this._apiPut("/api/settings",{...m,...s.showPlanUsageLimits?{statusLineTelemetry:!0}:{},notificationPreferences:i,voiceSettings:o});if(s.tunnelEnabled&&await this._handleTunnelEnableRefusal(I)){s.tunnelEnabled=!1,this.saveAppSettingsToStorage(s);const E=document.getElementById("appSettingsTunnelEnabled");E&&(E.checked=!1),this.closeAppSettings();return}await this.saveModelConfigFromSettings(),this.showToast("Settings saved","success"),s.tunnelEnabled&&this.showToast("Tunnel starting \u2014 QR code will appear when ready...","info")}catch{this.showToast("Settings saved locally","warning")}this.closeAppSettings(),VoiceInput.refreshClaudeStatus(),s.gestureControlEnabled!==t&&(this.showToast(s.gestureControlEnabled?"Enabling gesture control \u2014 reloading\u2026":"Disabling gesture control \u2014 reloading\u2026","info"),setTimeout(()=>location.reload(),400))},async loadModelConfigForSettings(){try{const t=await(await fetch("/api/execution/model-config")).json();if(t.success&&t.data){const n=t.data,s=document.getElementById("appSettingsDefaultModel");s&&(s.value=n.defaultModel||"");const o=document.getElementById("appSettingsShowModelRecommendations");o&&(o.checked=n.showRecommendations??!0);const i=n.agentTypeOverrides||{},a=document.getElementById("appSettingsModelExplore"),l=document.getElementById("appSettingsModelImplement"),c=document.getElementById("appSettingsModelTest"),d=document.getElementById("appSettingsModelReview");a&&(a.value=i.explore||""),l&&(l.value=i.implement||""),c&&(c.value=i.test||""),d&&(d.value=i.review||"")}}catch(e){console.warn("Failed to load model config:",e)}},async saveModelConfigFromSettings(){const e=document.getElementById("appSettingsDefaultModel"),t=document.getElementById("appSettingsShowModelRecommendations"),n=document.getElementById("appSettingsModelExplore"),s=document.getElementById("appSettingsModelImplement"),o=document.getElementById("appSettingsModelTest"),i=document.getElementById("appSettingsModelReview"),a={};n?.value&&(a.explore=n.value),s?.value&&(a.implement=s.value),o?.value&&(a.test=o.value),i?.value&&(a.review=i.value);const l={defaultModel:e?.value||"",showRecommendations:t?.checked??!0,agentTypeOverrides:a};try{await fetch("/api/execution/model-config",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(l)})}catch(c){console.warn("Failed to save model config:",c)}},isRalphTrackerEnabledByDefault(){return this.loadAppSettingsFromStorage().ralphTrackerEnabled??!1},getSettingsStorageKey(){return MobileDetection.isHandheldDevice?.()??MobileDetection.getDeviceType()==="mobile"?"codeman-app-settings-mobile":"codeman-app-settings"},getDefaultSettings(){return MobileDetection.isHandheldDevice?.()??MobileDetection.getDeviceType()==="mobile"?{showFontControls:!1,showSystemStats:!1,showTokenCount:!1,showCost:!1,showMonitor:!1,showProjectInsights:!1,showFileBrowser:!1,showSubagents:!1,showUltracodeAgents:!1,ultracodeFloatingWindows:!1,showMultiMonitorButton:!1,showPlanUsageLimits:!1,showAttachmentsButton:!1,showFileViewerButton:!1,showRedrawButton:!1,showSessionButton:!1,showAwayDigestButton:!1,showCronButton:!1,mobileOverviewEnabled:!0,remoteAutoReconnect:!0,gestureControlEnabled:!1,subagentTrackingEnabled:!0,subagentActiveTabOnly:!0,imageWatcherEnabled:!1,ralphTrackerEnabled:!1,tabTwoRows:!1,cjkInputEnabled:!1,terminalWheelLocalScrollback:!1,webglRendererEnabled:!1,skin:"daylight-blue"}:{}},loadAppSettingsFromStorage(){if(this._cachedAppSettings)return this._cachedAppSettings;try{const e=this.getSettingsStorageKey(),t=localStorage.getItem(e);if(t)return this._cachedAppSettings=JSON.parse(t),this._cachedAppSettings}catch(e){console.error("Failed to load app settings:",e)}return this._cachedAppSettings=this.getDefaultSettings(),this._cachedAppSettings},saveAppSettingsToStorage(e){this._cachedAppSettings=e;try{const t=this.getSettingsStorageKey();localStorage.setItem(t,JSON.stringify(e))}catch(t){console.error("Failed to save app settings:",t)}},applySkin(){const e=this.loadAppSettingsFromStorage(),t=this.getDefaultSettings(),n=e.skin??t.skin??"daylight-blue";document.documentElement.setAttribute("data-skin",n),window.__codemanSkin=n;const s=getComputedStyle(document.documentElement).getPropertyValue("--bg-dark").trim();s&&document.querySelector('meta[name="theme-color"]')?.setAttribute("content",s);try{localStorage.setItem("codeman:skin",n)}catch{}typeof this.applyTerminalSkin=="function"&&this.applyTerminalSkin(n)},applyLocalization(){const e=this.loadAppSettingsFromStorage();window.CodemanI18n?.configure({language:e.language,displayName:e.displayName})&&this.notificationManager&&(this.notificationManager.originalTitle=document.title)},planUsageChipEnabled(e=null){return(e??this.loadAppSettingsFromStorage()).showPlanUsageLimits??this.getDefaultSettings().showPlanUsageLimits??!0},applyHeaderVisibilitySettings(){const e=this.loadAppSettingsFromStorage(),t=this.getDefaultSettings(),n=e.showTabDetachButton??t.showTabDetachButton??!1;document.documentElement.classList.toggle("tabs-show-detach",n);const s=MobileDetection.getDeviceType()!=="desktop",o=s?!1:e.showFontControls??t.showFontControls??!1,i=s?!1:e.showSystemStats??t.showSystemStats??!0,a=s?!1:e.showTokenCount??t.showTokenCount??!1,l=document.querySelector(".header-font-controls"),c=document.getElementById("headerSystemStats"),d=document.getElementById("headerTokens");l&&(l.style.display=o?"":"none"),c&&(c.style.display=i?"":"none"),d&&(d.style.display=a?"":"none");const r=e.showLifecycleLog??t.showLifecycleLog??!1,u=document.querySelector(".btn-lifecycle-log");u&&(u.style.display=r?"":"none");const g=e.showResponseViewer??t.showResponseViewer??!1,p=document.querySelector(".btn-response-viewer-header");p&&p.classList.toggle("btn-response-viewer-header--hidden",!g);const S=e.showAttachmentsButton??t.showAttachmentsButton??!1,y=document.getElementById("attachmentsHistoryBtn");y&&y.classList.toggle("btn-attachments-history--hidden",!S);const b=e.showFileViewerButton??t.showFileViewerButton??!0,f=document.querySelector(".btn-file-viewer");f&&f.classList.toggle("btn-file-viewer--hidden",!b);const w=e.showMultiMonitorButton??t.showMultiMonitorButton??!1,h=document.querySelector(".btn-multimonitor");h&&h.classList.toggle("btn-multimonitor--hidden",!w);const B=e.showUltracodeAgents??t.showUltracodeAgents??!1,v=document.querySelector(".btn-ultracode-agents");v&&v.classList.toggle("btn-ultracode-agents--hidden",!B);const m=document.querySelector(".btn-readmymind");m&&m.classList.toggle("btn-readmymind--hidden",e.readMyMindEnabled!==!0),typeof KeyboardAccessoryBar<"u"&&KeyboardAccessoryBar.syncReadMyMind?.();const I=this.planUsageChipEnabled(e),E=document.getElementById("planUsageChip");E&&E.classList.toggle("header-plan-usage--hidden",!I);const A=e.showRedrawButton??t.showRedrawButton??!1,T=document.querySelector(".btn-redraw-terminal");T&&T.classList.toggle("btn-redraw-terminal--hidden",!A);const M=e.showSessionButton??t.showSessionButton??!1,k=document.querySelector(".btn-session-manager");k&&k.classList.toggle("btn-session-manager--hidden",!M);const x=e.showAwayDigestButton??t.showAwayDigestButton??!1,_=document.querySelector(".btn-away-digest");_&&_.classList.toggle("btn-away-digest--hidden",!x);const R=e.showCronButton??t.showCronButton??!1,C=document.querySelector(".btn-cron");C&&C.classList.toggle("btn-cron--hidden",!R);const L=document.querySelector(".btn-notifications");if(L&&(L.style.display="none"),!(this.notificationManager?.preferences?.enabled??!0)){const P=document.getElementById("notifDrawer");P&&P.classList.remove("open")}},applyTabWrapSettings(){const e=this.loadAppSettingsFromStorage(),t=this.getDefaultSettings(),s=MobileDetection.getDeviceType()==="desktop"?e.tabTwoRows??t.tabTwoRows??!1:!1,o=this._tallTabsEnabled;this._tallTabsEnabled=s;const i=document.getElementById("sessionTabs");i&&(i.classList.toggle("tabs-two-rows",s),i.classList.toggle("tabs-show-folder",s)),o!==void 0&&o!==s&&this._fullRenderSessionTabs()},applyMonitorVisibility(){const e=this.loadAppSettingsFromStorage(),t=this.getDefaultSettings(),n=e.showMonitor??t.showMonitor??!1,s=e.showSubagents??t.showSubagents??!1,o=e.showFileBrowser??t.showFileBrowser??!1,i=document.getElementById("monitorPanel");i&&(i.style.display=n?"":"none",n?i.classList.add("open"):i.classList.remove("open"));const a=document.getElementById("subagentsPanel");a&&(s?a.classList.remove("hidden"):a.classList.add("hidden"));const l=e.showUltracodeAgents??t.showUltracodeAgents??!1,c=document.getElementById("ultracodeAgentsPanel");c&&(l?c.classList.remove("hidden"):(c.classList.remove("open"),c.classList.add("hidden"))),e.ultracodeFloatingWindows??t.ultracodeFloatingWindows??!1?typeof this.syncAllUltracodeFloatingWindows=="function"&&this.syncAllUltracodeFloatingWindows():typeof this.removeAllUltracodeWindows=="function"&&this.removeAllUltracodeWindows();const r=document.getElementById("fileBrowserPanel");if(r)if(o&&this.activeSessionId){if(r.classList.add("visible"),this.loadFileBrowser(this.activeSessionId),!this.fileBrowserDragListeners){const u=r.querySelector(".file-browser-header");if(u){const g=()=>{if(!r.style.left){const p=r.getBoundingClientRect();r.style.left=`${p.left}px`,r.style.top=`${p.top}px`,r.style.right="auto"}};u.addEventListener("mousedown",g),u.addEventListener("touchstart",g,{passive:!0}),this.fileBrowserDragListeners=this.makeWindowDraggable(r,u),this.fileBrowserDragListeners._onFirstDrag=g}}}else r.classList.remove("visible")},closeMonitor(){const e=document.getElementById("monitorPanel");e&&(e.classList.remove("open"),e.style.display="none");const t=this.loadAppSettingsFromStorage();t.showMonitor=!1,this.saveAppSettingsToStorage(t)},closeSubagentsPanel(){const e=document.getElementById("subagentsPanel");e&&(e.classList.remove("open"),e.classList.add("hidden")),this.subagentPanelVisible=!1;const t=this.loadAppSettingsFromStorage();t.showSubagents=!1,this.saveAppSettingsToStorage(t)},async clearAllSubagents(){const e=this.subagents.size;if(e===0){this.showToast("No subagents to clear","info");return}if(confirm(`Clear all ${e} tracked subagent(s)? This removes them from the UI but does not affect running processes.`))try{const n=await(await fetch("/api/subagents",{method:"DELETE"})).json();n.success?(this.subagents.clear(),this.subagentActivity.clear(),this.subagentToolResults.clear(),this.cleanupAllFloatingWindows(),this.renderSubagentPanel(),this.renderMonitorSubagents(),this.updateSubagentBadge(),this.showToast(`Cleared ${n.data.cleared} subagent(s)`,"success")):this.showToast("Failed to clear subagents: "+n.error,"error")}catch{this.showToast("Failed to clear subagents","error")}},toggleSubagentsPanel(){const e=document.getElementById("subagentsPanel"),t=document.getElementById("subagentsToggleBtn");if(e){if(e.classList.contains("hidden")){e.classList.remove("hidden");const n=this.loadAppSettingsFromStorage();n.showSubagents=!0,this.saveAppSettingsToStorage(n)}e.classList.toggle("open"),this.subagentPanelVisible=e.classList.contains("open"),t&&(t.innerHTML=this.subagentPanelVisible?"▼":"▲"),this.subagentPanelVisible&&this.renderSubagentPanel()}},async loadAppSettingsFromServer(e=null){try{if((MobileDetection.isHandheldDevice?.()??MobileDetection.getDeviceType()==="mobile")&&!localStorage.getItem("codeman:planUsagePerDeviceMigrated")){const t=this.loadAppSettingsFromStorage();t&&t.showPlanUsageLimits&&(t.showPlanUsageLimits=!1,this.saveAppSettingsToStorage(t)),localStorage.setItem("codeman:planUsagePerDeviceMigrated","1")}}catch{}try{const t=e?await e:await fetch("/api/settings").then(n=>n.ok?n.json():null).then(n=>n?.success===!0?n.data:n);if(t){const{notificationPreferences:n,voiceSettings:s,respawnPresets:o,runMode:i,...a}=t,l=new Set(["showFontControls","showSystemStats","showTokenCount","showCost","showLifecycleLog","showResponseViewer","showRedrawButton","showMonitor","showProjectInsights","showFileBrowser","showSubagents","subagentActiveTabOnly","tabTwoRows","localEchoEnabled","cjkInputEnabled","extendedKeyboardBar","skin","showPlanUsageLimits","showAttachmentsButton","showFileViewerButton","webglRendererEnabled","language","terminalWheelLocalScrollback","showSessionButton","showAwayDigestButton","showCronButton","showTabDetachButton","mobileOverviewEnabled","sessionLineageLines"]);delete a.showPlanUsageLimits;const c=this.loadAppSettingsFromStorage(),d={...c};for(const[r,u]of Object.entries(a))l.has(r)&&r in c||(d[r]=u);if(this.saveAppSettingsToStorage(d),n&&this.notificationManager&&(localStorage.getItem(this.notificationManager.getStorageKey())||(this.notificationManager.preferences=this.notificationManager.normalizePreferences(n),this.notificationManager.savePreferences())),s){const r=localStorage.getItem("codeman-voice-settings");(!r||!JSON.parse(r).apiKey)&&VoiceInput._saveDeepgramConfig(s)}if(o&&Array.isArray(o))this._serverRespawnPresets=o,localStorage.setItem("codeman-respawn-presets",JSON.stringify(o));else{const r=localStorage.getItem("codeman-respawn-presets");if(r){const u=JSON.parse(r);u.length>0&&(this._serverRespawnPresets=u,this._apiPut("/api/settings",{respawnPresets:u}).catch(()=>{}))}}if(i){this.runMode=i;try{localStorage.setItem("codeman_runMode",i)}catch{}this._applyRunMode()}return d}}catch(t){console.error("Failed to load settings from server:",t)}return this.loadAppSettingsFromStorage()},async loadSubagentWindowStates(){let e=null;try{const t=await fetch("/api/subagent-window-states");if(t.ok){const n=await t.json();e=n?.success===!0?n.data:n,localStorage.setItem("codeman-subagent-window-states",JSON.stringify(e))}}catch(t){console.error("Failed to load subagent window states from server:",t)}if(!e)try{const t=localStorage.getItem("codeman-subagent-window-states");t&&(e=JSON.parse(t))}catch(t){console.error("Failed to load subagent window states from localStorage:",t)}return e||{minimized:{},open:[]}},async saveSubagentParentMap(){const e=Object.fromEntries(this.subagentParentMap);try{localStorage.setItem("codeman-subagent-parents",JSON.stringify(e))}catch(t){console.error("Failed to save subagent parents to localStorage:",t)}try{await fetch("/api/subagent-parents",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})}catch(t){console.error("Failed to save subagent parents to server:",t)}},async loadSubagentParentMap(){let e=null;try{const t=await fetch("/api/subagent-parents");if(t.ok){const n=await t.json();e=n?.success===!0?n.data:n,localStorage.setItem("codeman-subagent-parents",JSON.stringify(e))}}catch(t){console.error("Failed to load subagent parents from server:",t)}if(!e)try{const t=localStorage.getItem("codeman-subagent-parents");t&&(e=JSON.parse(t))}catch(t){console.error("Failed to load subagent parents from localStorage:",t)}if(e&&typeof e=="object")for(const[t,n]of Object.entries(e))this.sessions.has(n)&&this.subagents.has(t)&&this.subagentParentMap.set(t,n)},getAgentParentSessionId(e){return this.subagentParentMap.get(e)||null},setAgentParentSessionId(e,t){if(!e||!t||this.subagentParentMap.has(e))return;this.subagentParentMap.set(e,t),this.saveSubagentParentMap();const n=this.subagents.get(e);if(n){n.parentSessionId=t;const s=this.sessions.get(t);s&&(n.parentSessionName=this.getSessionName(s)),this.subagents.set(e,n)}},showHelp(){const e=document.getElementById("helpModal");e.classList.add("active"),this.activeFocusTrap=new FocusTrap(e),this.activeFocusTrap.activate()},closeHelp(){document.getElementById("helpModal").classList.remove("active"),this.activeFocusTrap&&(this.activeFocusTrap.deactivate(),this.activeFocusTrap=null)},renderShortcutSettingsList(){const e=document.getElementById("appSettingsShortcutsList");if(!e)return;const t=this.getShortcutRegistry?this.getShortcutRegistry():typeof DEFAULT_SHORTCUTS<"u"?DEFAULT_SHORTCUTS:[],n=this.readShortcutOverridesFromSettings();e.innerHTML=t.map(s=>{const o=s.displayBindings?s.displayBindings.join(" / "):(s.bindings||[]).map(c=>[...c.modifiers||[],c.key||c.code||""].join("+")).join(" / "),i=!!s.action&&Array.isArray(s.bindings),a=!!n[s.id],l=i?`<button type="button" class="shortcut-capture-btn" data-shortcut-action="capture" title="Capture new binding">Edit</button>
|
|
62
|
+
<button type="button" class="shortcut-reset-btn" data-shortcut-action="reset" title="Reset to default"${a?"":" disabled"}>Reset</button>
|
|
63
|
+
<input class="shortcut-enabled-checkbox" type="checkbox" ${s.disabled?"":"checked"} data-shortcut-action="toggle" title="Enable/disable">`:"";return`<div class="shortcut-setting-row${i?"":" shortcut-setting-row--fixed"}" data-shortcut-id="${escapeHtml(s.id)}">
|
|
64
|
+
<label class="shortcut-setting-label">${escapeHtml(s.label)}</label>
|
|
65
|
+
<input class="shortcut-binding-input" type="text" readonly value="${escapeHtml(o)}" placeholder="(none)" data-id="${escapeHtml(s.id)}">
|
|
66
|
+
${l}
|
|
67
|
+
</div>`}).join(""),this._wireShortcutSettingsList(e)},_wireShortcutSettingsList(e){e.dataset.shortcutListenersAdded||(e.dataset.shortcutListenersAdded="true",e.addEventListener("click",t=>{const n=t.target?.closest?.("[data-shortcut-action]");if(!n)return;const s=n.closest?.("[data-shortcut-id]")?.dataset?.shortcutId;s&&(n.dataset.shortcutAction==="capture"?this.startShortcutCapture(s):n.dataset.shortcutAction==="reset"&&this.resetShortcutOverride(s))}),e.addEventListener("change",t=>{const n=t.target;if(!n?.matches?.('[data-shortcut-action="toggle"]'))return;const s=n.closest?.("[data-shortcut-id]")?.dataset?.shortcutId;s&&this.toggleShortcutEnabled(s,n.checked)}))},readShortcutOverridesFromSettings(){return this.loadAppSettingsFromStorage().shortcutOverrides||{}},startShortcutCapture(e){const t=document.querySelector(`.shortcut-binding-input[data-id="${e}"]`);if(!t)return;t.value="Press keys\u2026",t.focus(),this._capturingShortcutId=e;const n=s=>{s.preventDefault(),s.stopPropagation(),!(s.key==="Control"||s.key==="Shift"||s.key==="Alt"||s.key==="Meta")&&(t.removeEventListener("keydown",n),this.onShortcutCaptureKeydown(s,e))};t.addEventListener("keydown",n)},onShortcutCaptureKeydown(e,t){if(e.preventDefault(),e.stopPropagation(),this._capturingShortcutId=null,e.key==="Escape"){this.renderShortcutSettingsList();return}if(!e.ctrlKey&&!e.metaKey&&!e.altKey){this.renderShortcutSettingsList(),this.showToast?.("Shortcut must include Ctrl, Cmd, or Alt","error");return}const n=[];e.ctrlKey&&n.push("ctrl"),e.metaKey&&n.push("meta"),e.shiftKey&&n.push("shift"),e.altKey&&n.push("alt");const s=this.loadAppSettingsFromStorage(),o={...s.shortcutOverrides||{}};o[t]={...o[t]||{},bindings:[{modifiers:n,key:e.key,code:e.code}]},s.shortcutOverrides=o,this.saveAppSettingsToStorage(s),this.renderShortcutSettingsList()},resetShortcutOverride(e){const t=this.loadAppSettingsFromStorage(),n={...t.shortcutOverrides||{}};delete n[e],t.shortcutOverrides=n,this.saveAppSettingsToStorage(t),this.renderShortcutSettingsList()},toggleShortcutEnabled(e,t){const n=this.loadAppSettingsFromStorage(),s={...n.shortcutOverrides||{}};s[e]={...s[e]||{},disabled:!t},n.shortcutOverrides=s,this.saveAppSettingsToStorage(n),this.renderShortcutSettingsList()},closeAllPanels(){this.closeSessionOptions(),this.closeAppSettings(),this.cancelCloseSession(),this.closeTokenStats(),document.getElementById("monitorPanel").classList.remove("open");const e=document.getElementById("subagentsPanel");e&&e.classList.remove("open"),this.subagentPanelVisible=!1}});
|
|
Binary file
|
|
Binary file
|