aicodeman 0.9.12 → 0.9.14
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 +1 -0
- package/README.zh-CN.md +1 -0
- package/dist/config/hook-secret.d.ts +28 -0
- package/dist/config/hook-secret.d.ts.map +1 -0
- package/dist/config/hook-secret.js +63 -0
- package/dist/config/hook-secret.js.map +1 -0
- package/dist/hooks-config.d.ts +3 -2
- package/dist/hooks-config.d.ts.map +1 -1
- package/dist/hooks-config.js +10 -2
- package/dist/hooks-config.js.map +1 -1
- package/dist/respawn-controller.d.ts.map +1 -1
- package/dist/respawn-controller.js +12 -0
- package/dist/respawn-controller.js.map +1 -1
- package/dist/session-auto-ops.d.ts +43 -1
- package/dist/session-auto-ops.d.ts.map +1 -1
- package/dist/session-auto-ops.js +162 -1
- package/dist/session-auto-ops.js.map +1 -1
- package/dist/session-cli-builder.d.ts.map +1 -1
- package/dist/session-cli-builder.js +5 -0
- package/dist/session-cli-builder.js.map +1 -1
- package/dist/session.d.ts +41 -4
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +129 -5
- package/dist/session.js.map +1 -1
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +3 -0
- package/dist/tmux-manager.js.map +1 -1
- package/dist/types/session.d.ts +4 -0
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js.map +1 -1
- package/dist/usage-limit-patterns.d.ts +48 -0
- package/dist/usage-limit-patterns.d.ts.map +1 -0
- package/dist/usage-limit-patterns.js +184 -0
- package/dist/usage-limit-patterns.js.map +1 -0
- package/dist/web/middleware/auth.d.ts +9 -1
- package/dist/web/middleware/auth.d.ts.map +1 -1
- package/dist/web/middleware/auth.js +58 -8
- package/dist/web/middleware/auth.js.map +1 -1
- package/dist/web/network-auth-policy.d.ts +6 -0
- package/dist/web/network-auth-policy.d.ts.map +1 -1
- package/dist/web/network-auth-policy.js +10 -0
- package/dist/web/network-auth-policy.js.map +1 -1
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/app.a23f8bf6.js +35 -0
- package/dist/web/public/app.a23f8bf6.js.br +0 -0
- package/dist/web/public/app.a23f8bf6.js.gz +0 -0
- package/dist/web/public/{constants.74211deb.js → constants.8fa1a65f.js} +4 -0
- package/dist/web/public/constants.8fa1a65f.js.br +0 -0
- package/dist/web/public/constants.8fa1a65f.js.gz +0 -0
- package/dist/web/public/image-input.0ea86695.js.gz +0 -0
- package/dist/web/public/index.html +39 -34
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.b8686b5e.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.bc753cc7.js.gz +0 -0
- package/dist/web/public/{mobile-handlers.d54d97d6.js → mobile-handlers.763a7439.js} +10 -2
- package/dist/web/public/mobile-handlers.763a7439.js.br +0 -0
- package/dist/web/public/mobile-handlers.763a7439.js.gz +0 -0
- package/dist/web/public/mobile.c7513aed.css +1 -0
- package/dist/web/public/mobile.c7513aed.css.br +0 -0
- package/dist/web/public/mobile.c7513aed.css.gz +0 -0
- package/dist/web/public/notification-manager.9c984ac2.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/panels-ui.6bb3169f.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/respawn-ui.2d249da9.js.gz +0 -0
- package/dist/web/public/{session-ui.512816d8.js → session-ui.34f25fdf.js} +1 -1
- package/dist/web/public/session-ui.34f25fdf.js.br +0 -0
- package/dist/web/public/session-ui.34f25fdf.js.gz +0 -0
- package/dist/web/public/{settings-ui.21b009ca.js → settings-ui.3a341938.js} +8 -8
- package/dist/web/public/settings-ui.3a341938.js.br +0 -0
- package/dist/web/public/settings-ui.3a341938.js.gz +0 -0
- package/dist/web/public/styles.c14ecd8a.css +1 -0
- package/dist/web/public/styles.c14ecd8a.css.br +0 -0
- package/dist/web/public/styles.c14ecd8a.css.gz +0 -0
- package/dist/web/public/subagent-windows.a366a4ad.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/terminal-ui.1f5b45cf.js +3 -0
- package/dist/web/public/terminal-ui.1f5b45cf.js.br +0 -0
- package/dist/web/public/terminal-ui.1f5b45cf.js.gz +0 -0
- package/dist/web/public/upload.html.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-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-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.085e9e73.js.gz +0 -0
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +52 -2
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/routes/system-routes.d.ts.map +1 -1
- package/dist/web/routes/system-routes.js +19 -0
- package/dist/web/routes/system-routes.js.map +1 -1
- package/dist/web/routes/ws-routes.d.ts.map +1 -1
- package/dist/web/routes/ws-routes.js +7 -0
- package/dist/web/routes/ws-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +4 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +4 -0
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts +1 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +17 -1
- package/dist/web/server.js.map +1 -1
- package/dist/web/session-listener-wiring.d.ts +11 -0
- package/dist/web/session-listener-wiring.d.ts.map +1 -1
- package/dist/web/session-listener-wiring.js +25 -0
- package/dist/web/session-listener-wiring.js.map +1 -1
- package/dist/web/sse-events.d.ts +9 -0
- package/dist/web/sse-events.d.ts.map +1 -1
- package/dist/web/sse-events.js +9 -0
- package/dist/web/sse-events.js.map +1 -1
- package/package.json +1 -1
- package/dist/web/public/app.a8663e79.js +0 -35
- package/dist/web/public/app.a8663e79.js.br +0 -0
- package/dist/web/public/app.a8663e79.js.gz +0 -0
- package/dist/web/public/constants.74211deb.js.br +0 -0
- package/dist/web/public/constants.74211deb.js.gz +0 -0
- package/dist/web/public/mobile-handlers.d54d97d6.js.br +0 -0
- package/dist/web/public/mobile-handlers.d54d97d6.js.gz +0 -0
- package/dist/web/public/mobile.959f6fe2.css +0 -1
- package/dist/web/public/mobile.959f6fe2.css.br +0 -0
- package/dist/web/public/mobile.959f6fe2.css.gz +0 -0
- package/dist/web/public/session-ui.512816d8.js.br +0 -0
- package/dist/web/public/session-ui.512816d8.js.gz +0 -0
- package/dist/web/public/settings-ui.21b009ca.js.br +0 -0
- package/dist/web/public/settings-ui.21b009ca.js.gz +0 -0
- package/dist/web/public/styles.f3a0faa3.css +0 -1
- package/dist/web/public/styles.f3a0faa3.css.br +0 -0
- package/dist/web/public/styles.f3a0faa3.css.gz +0 -0
- package/dist/web/public/terminal-ui.0e930cb3.js +0 -3
- package/dist/web/public/terminal-ui.0e930cb3.js.br +0 -0
- package/dist/web/public/terminal-ui.0e930cb3.js.gz +0 -0
|
@@ -11,19 +11,28 @@ import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
|
11
11
|
import { StaleExpirationMap } from '../../utils/index.js';
|
|
12
12
|
import { isAllowedRequestHost, isAllowedRequestOrigin } from '../network-auth-policy.js';
|
|
13
13
|
import { AUTH_SESSION_TTL_MS, MAX_AUTH_SESSIONS, AUTH_FAILURE_MAX, AUTH_FAILURE_WINDOW_MS, } from '../../config/auth-config.js';
|
|
14
|
+
import { getHookSecret, HOOK_SECRET_HEADER } from '../../config/hook-secret.js';
|
|
14
15
|
// Auth session cookie name
|
|
15
16
|
export const AUTH_COOKIE_NAME = 'codeman_session';
|
|
16
17
|
/**
|
|
17
18
|
* Register HTTP Basic Auth middleware with session cookies and rate limiting.
|
|
18
19
|
* Only active when CODEMAN_PASSWORD is set.
|
|
19
20
|
*
|
|
21
|
+
* @param getTunnelRunning - returns true while a managed tunnel is active. Used
|
|
22
|
+
* to gate the `/api/hook-event` localhost bypass: when a tunnel is up, tunneled
|
|
23
|
+
* internet traffic reaches the loopback origin with `req.ip === 127.0.0.1`, so
|
|
24
|
+
* the bypass additionally requires the shared hook secret (COD-54). When no
|
|
25
|
+
* tunnel is running (loopback-only, the normal case) the plain localhost bypass
|
|
26
|
+
* is kept so already-deployed (pre-secret) hooks + the loop channel keep working.
|
|
27
|
+
* Optional; defaults to "no tunnel" (unchanged behavior) when omitted.
|
|
20
28
|
* @returns AuthState for lifecycle management (dispose on server stop)
|
|
21
29
|
*/
|
|
22
|
-
export function registerAuthMiddleware(app, https) {
|
|
30
|
+
export function registerAuthMiddleware(app, https, getTunnelRunning = () => false) {
|
|
23
31
|
const state = {
|
|
24
32
|
authSessions: null,
|
|
25
33
|
authFailures: null,
|
|
26
34
|
qrAuthFailures: null,
|
|
35
|
+
hookSecretFailures: null,
|
|
27
36
|
};
|
|
28
37
|
const authPassword = process.env.CODEMAN_PASSWORD;
|
|
29
38
|
if (!authPassword)
|
|
@@ -45,22 +54,63 @@ export function registerAuthMiddleware(app, https) {
|
|
|
45
54
|
ttlMs: AUTH_FAILURE_WINDOW_MS,
|
|
46
55
|
refreshOnGet: false,
|
|
47
56
|
});
|
|
57
|
+
// Separate hook-secret failure counter (COD-54). MUST NOT share authFailures:
|
|
58
|
+
// legacy (pre-secret) hook configs fire constantly from 127.0.0.1, and counting
|
|
59
|
+
// their 401s against the shared bucket would 429 every cookie-less request from
|
|
60
|
+
// loopback — locking out the Basic-Auth login path (and, through a tunnel, every
|
|
61
|
+
// client, since tunneled traffic also arrives as 127.0.0.1).
|
|
62
|
+
state.hookSecretFailures = new StaleExpirationMap({
|
|
63
|
+
ttlMs: AUTH_FAILURE_WINDOW_MS,
|
|
64
|
+
refreshOnGet: false,
|
|
65
|
+
});
|
|
48
66
|
const authSessions = state.authSessions;
|
|
49
67
|
const authFailures = state.authFailures;
|
|
50
|
-
|
|
51
|
-
|
|
68
|
+
const hookSecretFailures = state.hookSecretFailures;
|
|
69
|
+
function sendAuthRateLimit(reply, clientIp, failures = authFailures) {
|
|
70
|
+
const remainingMs = failures.getRemainingTtl(clientIp) ?? AUTH_FAILURE_WINDOW_MS;
|
|
52
71
|
const retryAfterSeconds = Math.max(1, Math.ceil(remainingMs / 1000));
|
|
53
72
|
reply.header('Retry-After', String(retryAfterSeconds));
|
|
54
73
|
reply.code(429).send('Too Many Requests — try again later');
|
|
55
74
|
}
|
|
56
75
|
app.addHook('onRequest', (req, reply, done) => {
|
|
57
|
-
// Hook events come from local Claude Code hooks (curl from localhost) — no
|
|
58
|
-
//
|
|
59
|
-
//
|
|
76
|
+
// Hook events come from local Claude Code hooks (curl from localhost) — no
|
|
77
|
+
// Basic-Auth credentials available. Validated downstream by HookEventSchema.
|
|
78
|
+
//
|
|
79
|
+
// COD-54: the bare localhost bypass is unsafe while a tunnel is running, because
|
|
80
|
+
// `cloudflared --url http://127.0.0.1:port` proxies internet traffic INTO the
|
|
81
|
+
// loopback origin, so a tunneled request arrives with req.ip === 127.0.0.1 and
|
|
82
|
+
// would pass. So:
|
|
83
|
+
// - tunnel running → bypass requires the shared hook secret (local hooks present
|
|
84
|
+
// it via the X-Codeman-Hook-Secret header; internet traffic can't know it),
|
|
85
|
+
// - tunnel not running (loopback-only, the normal case) → keep the plain
|
|
86
|
+
// localhost bypass so already-deployed (pre-secret) hooks + the loop's own
|
|
87
|
+
// credential-less hook channel keep working.
|
|
60
88
|
if (req.url === '/api/hook-event' && req.method === 'POST') {
|
|
61
89
|
const ip = req.ip;
|
|
62
|
-
|
|
63
|
-
|
|
90
|
+
const isLoopback = ip === '127.0.0.1' || ip === '::1' || ip === '::ffff:127.0.0.1';
|
|
91
|
+
if (isLoopback) {
|
|
92
|
+
if (!getTunnelRunning()) {
|
|
93
|
+
// Loopback-only: unchanged behavior.
|
|
94
|
+
done();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// Tunnel up: require the shared secret (constant-time compare).
|
|
98
|
+
const presented = Buffer.from(req.headers[HOOK_SECRET_HEADER.toLowerCase()]?.toString() ?? '');
|
|
99
|
+
const expected = Buffer.from(getHookSecret());
|
|
100
|
+
if (presented.length === expected.length && timingSafeEqual(presented, expected)) {
|
|
101
|
+
done();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
// Wrong/absent secret while tunneled — rate-limit per IP in the DEDICATED
|
|
105
|
+
// hook bucket (never authFailures, which would lock out the login path).
|
|
106
|
+
const hookIp = req.ip;
|
|
107
|
+
const hookFailures = hookSecretFailures.get(hookIp) ?? 0;
|
|
108
|
+
if (hookFailures >= AUTH_FAILURE_MAX) {
|
|
109
|
+
sendAuthRateLimit(reply, hookIp, hookSecretFailures);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
hookSecretFailures.set(hookIp, hookFailures + 1);
|
|
113
|
+
reply.code(401).send('Unauthorized: hook secret required');
|
|
64
114
|
return;
|
|
65
115
|
}
|
|
66
116
|
// Non-localhost hook requests fall through to normal auth
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/web/middleware/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAmB,MAAM,2BAA2B,CAAC;AAC1G,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/web/middleware/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
export declare function isExplicitlyEnabled(value: string | undefined): boolean;
|
|
2
|
+
/**
|
|
3
|
+
* True when unauthenticated network exposure is acceptable: either a password is
|
|
4
|
+
* set (auth active) or the operator explicitly acknowledged it. Used by the
|
|
5
|
+
* tunnel-enable guard (COD-55) to refuse publishing an unauthenticated public URL.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isUnauthenticatedNetworkAcknowledged(allowFlag?: boolean): boolean;
|
|
2
8
|
export declare function isLoopbackBindHost(host: string): boolean;
|
|
3
9
|
/**
|
|
4
10
|
* Hostname suffixes that are always accepted by the Host/Origin allowlist. These
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"network-auth-policy.d.ts","sourceRoot":"","sources":["../../src/web/network-auth-policy.ts"],"names":[],"mappings":"AAIA,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEtE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAYxD;AAED;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,UAAyD,CAAC;AAEpG,yFAAyF;AACzF,MAAM,WAAW,UAAU;IACzB,oFAAoF;IACpF,QAAQ,EAAE,MAAM,CAAC;IACjB,kGAAkG;IAClG,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAiBnF;AAED,8FAA8F;AAC9F,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,UAAU,CAcvF;AAwBD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAIhG;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAQpG"}
|
|
1
|
+
{"version":3,"file":"network-auth-policy.d.ts","sourceRoot":"","sources":["../../src/web/network-auth-policy.ts"],"names":[],"mappings":"AAIA,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEtE;AAED;;;;GAIG;AACH,wBAAgB,oCAAoC,CAAC,SAAS,UAAQ,GAAG,OAAO,CAG/E;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAYxD;AAED;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,UAAyD,CAAC;AAEpG,yFAAyF;AACzF,MAAM,WAAW,UAAU;IACzB,oFAAoF;IACpF,QAAQ,EAAE,MAAM,CAAC;IACjB,kGAAkG;IAClG,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAiBnF;AAED,8FAA8F;AAC9F,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,UAAU,CAcvF;AAwBD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAIhG;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAQpG"}
|
|
@@ -3,6 +3,16 @@ const EXPLICIT_TRUE_VALUES = new Set(['1', 'true', 'yes', 'on']);
|
|
|
3
3
|
export function isExplicitlyEnabled(value) {
|
|
4
4
|
return value !== undefined && EXPLICIT_TRUE_VALUES.has(value.trim().toLowerCase());
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* True when unauthenticated network exposure is acceptable: either a password is
|
|
8
|
+
* set (auth active) or the operator explicitly acknowledged it. Used by the
|
|
9
|
+
* tunnel-enable guard (COD-55) to refuse publishing an unauthenticated public URL.
|
|
10
|
+
*/
|
|
11
|
+
export function isUnauthenticatedNetworkAcknowledged(allowFlag = false) {
|
|
12
|
+
if (process.env.CODEMAN_PASSWORD)
|
|
13
|
+
return true;
|
|
14
|
+
return allowFlag || isExplicitlyEnabled(process.env.CODEMAN_ALLOW_UNAUTHENTICATED_NETWORK);
|
|
15
|
+
}
|
|
6
16
|
export function isLoopbackBindHost(host) {
|
|
7
17
|
const normalized = host
|
|
8
18
|
.trim()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"network-auth-policy.js","sourceRoot":"","sources":["../../src/web/network-auth-policy.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"network-auth-policy.js","sourceRoot":"","sources":["../../src/web/network-auth-policy.ts"],"names":[],"mappings":"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"}
|
|
Binary file
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";const _crashDiag={_entries:[],_maxEntries:50,log(c){const e=`${new Date().toISOString().slice(11,23)} ${c}`;this._entries.push(e),this._entries.length>this._maxEntries&&this._entries.shift();try{localStorage.setItem("codeman-crash-diag",this._entries.join(`
|
|
2
|
+
`))}catch{}}};try{const c=localStorage.getItem("codeman-crash-diag");c&&console.log(`[CRASH-DIAG] Previous session breadcrumbs:
|
|
3
|
+
`+c)}catch{}if(_crashDiag.log("PAGE LOAD"),setInterval(()=>{try{localStorage.setItem("codeman-crash-heartbeat",String(Date.now())),_crashDiag._entries.length>0&&navigator.sendBeacon("/api/crash-diag",JSON.stringify({data:_crashDiag._entries.join(`
|
|
4
|
+
`)}))}catch{}},2e3),window.addEventListener("error",c=>{_crashDiag.log(`ERROR: ${c.message} at ${c.filename}:${c.lineno}`),console.error("[CRASH-DIAG] Uncaught error:",c.message,`
|
|
5
|
+
File:`,c.filename,":",c.lineno,":",c.colno,`
|
|
6
|
+
Stack:`,c.error?.stack)}),window.addEventListener("unhandledrejection",c=>{_crashDiag.log(`UNHANDLED: ${c.reason?.message||c.reason}`),console.error("[CRASH-DIAG] Unhandled promise rejection:",c.reason?.message||c.reason,`
|
|
7
|
+
Stack:`,c.reason?.stack)}),typeof PerformanceObserver<"u")try{new PerformanceObserver(e=>{for(const t of e.getEntries())t.duration>200&&(_crashDiag.log(`LONG_TASK: ${t.duration.toFixed(0)}ms`),console.warn(`[CRASH-DIAG] Long task: ${t.duration.toFixed(0)}ms (type: ${t.entryType}, name: ${t.name})`))}).observe({type:"longtask",buffered:!0})}catch{}const _origGetContext=HTMLCanvasElement.prototype.getContext;HTMLCanvasElement.prototype.getContext=function(c,...e){const t=_origGetContext.call(this,c,...e);return(c==="webgl2"||c==="webgl")&&(this.addEventListener("webglcontextlost",s=>{_crashDiag.log(`WEBGL_LOST: ${this.width}x${this.height}`),console.error("[CRASH-DIAG] WebGL context LOST on canvas",this.width,"x",this.height,"\u2014 prevented:",s.defaultPrevented)}),this.addEventListener("webglcontextrestored",()=>{_crashDiag.log("WEBGL_RESTORED"),console.warn("[CRASH-DIAG] WebGL context restored")})),t};const _SSE_HANDLER_MAP=[[SSE_EVENTS.INIT,"_onInit"],[SSE_EVENTS.SESSION_CREATED,"_onSessionCreated"],[SSE_EVENTS.SESSION_UPDATED,"_onSessionUpdated"],[SSE_EVENTS.SESSION_DELETED,"_onSessionDeleted"],[SSE_EVENTS.SESSION_TERMINAL,"_onSSETerminal"],[SSE_EVENTS.SESSION_NEEDS_REFRESH,"_onSSENeedsRefresh"],[SSE_EVENTS.SESSION_CLEAR_TERMINAL,"_onSSEClearTerminal"],[SSE_EVENTS.SESSION_COMPLETION,"_onSessionCompletion"],[SSE_EVENTS.SESSION_ERROR,"_onSessionError"],[SSE_EVENTS.SESSION_EXIT,"_onSessionExit"],[SSE_EVENTS.SESSION_IDLE,"_onSessionIdle"],[SSE_EVENTS.SESSION_WORKING,"_onSessionWorking"],[SSE_EVENTS.SESSION_AUTO_CLEAR,"_onSessionAutoClear"],[SSE_EVENTS.SESSION_LIMIT_PAUSE_SCHEDULED,"_onSessionLimitPauseScheduled"],[SSE_EVENTS.SESSION_LIMIT_RESUME,"_onSessionLimitResume"],[SSE_EVENTS.SESSION_LIMIT_RESUME_CANCELLED,"_onSessionLimitResumeCancelled"],[SSE_EVENTS.SESSION_CLI_INFO,"_onSessionCliInfo"],[SSE_EVENTS.SCHEDULED_CREATED,"_onScheduledCreated"],[SSE_EVENTS.SCHEDULED_UPDATED,"_onScheduledUpdated"],[SSE_EVENTS.SCHEDULED_COMPLETED,"_onScheduledCompleted"],[SSE_EVENTS.SCHEDULED_STOPPED,"_onScheduledStopped"],[SSE_EVENTS.RESPAWN_STARTED,"_onRespawnStarted"],[SSE_EVENTS.RESPAWN_STOPPED,"_onRespawnStopped"],[SSE_EVENTS.RESPAWN_STATE_CHANGED,"_onRespawnStateChanged"],[SSE_EVENTS.RESPAWN_CYCLE_STARTED,"_onRespawnCycleStarted"],[SSE_EVENTS.RESPAWN_BLOCKED,"_onRespawnBlocked"],[SSE_EVENTS.RESPAWN_AUTO_ACCEPT_SENT,"_onRespawnAutoAcceptSent"],[SSE_EVENTS.RESPAWN_DETECTION_UPDATE,"_onRespawnDetectionUpdate"],[SSE_EVENTS.RESPAWN_TIMER_STARTED,"_onRespawnTimerStarted"],[SSE_EVENTS.RESPAWN_TIMER_CANCELLED,"_onRespawnTimerCancelled"],[SSE_EVENTS.RESPAWN_TIMER_COMPLETED,"_onRespawnTimerCompleted"],[SSE_EVENTS.RESPAWN_ERROR,"_onRespawnError"],[SSE_EVENTS.RESPAWN_ACTION_LOG,"_onRespawnActionLog"],[SSE_EVENTS.TASK_CREATED,"_onTaskCreated"],[SSE_EVENTS.TASK_COMPLETED,"_onTaskCompleted"],[SSE_EVENTS.TASK_FAILED,"_onTaskFailed"],[SSE_EVENTS.TASK_UPDATED,"_onTaskUpdated"],[SSE_EVENTS.MUX_CREATED,"_onMuxCreated"],[SSE_EVENTS.MUX_KILLED,"_onMuxKilled"],[SSE_EVENTS.MUX_DIED,"_onMuxDied"],[SSE_EVENTS.MUX_STATS_UPDATED,"_onMuxStatsUpdated"],[SSE_EVENTS.SESSION_RALPH_LOOP_UPDATE,"_onRalphLoopUpdate"],[SSE_EVENTS.SESSION_RALPH_TODO_UPDATE,"_onRalphTodoUpdate"],[SSE_EVENTS.SESSION_RALPH_COMPLETION_DETECTED,"_onRalphCompletionDetected"],[SSE_EVENTS.SESSION_RALPH_STATUS_UPDATE,"_onRalphStatusUpdate"],[SSE_EVENTS.SESSION_CIRCUIT_BREAKER_UPDATE,"_onCircuitBreakerUpdate"],[SSE_EVENTS.SESSION_EXIT_GATE_MET,"_onExitGateMet"],[SSE_EVENTS.SESSION_BASH_TOOL_START,"_onBashToolStart"],[SSE_EVENTS.SESSION_BASH_TOOL_END,"_onBashToolEnd"],[SSE_EVENTS.SESSION_BASH_TOOLS_UPDATE,"_onBashToolsUpdate"],[SSE_EVENTS.HOOK_IDLE_PROMPT,"_onHookIdlePrompt"],[SSE_EVENTS.HOOK_PERMISSION_PROMPT,"_onHookPermissionPrompt"],[SSE_EVENTS.HOOK_ELICITATION_DIALOG,"_onHookElicitationDialog"],[SSE_EVENTS.HOOK_STOP,"_onHookStop"],[SSE_EVENTS.HOOK_TEAMMATE_IDLE,"_onHookTeammateIdle"],[SSE_EVENTS.HOOK_TASK_COMPLETED,"_onHookTaskCompleted"],[SSE_EVENTS.SUBAGENT_DISCOVERED,"_onSubagentDiscovered"],[SSE_EVENTS.SUBAGENT_UPDATED,"_onSubagentUpdated"],[SSE_EVENTS.SUBAGENT_TOOL_CALL,"_onSubagentToolCall"],[SSE_EVENTS.SUBAGENT_PROGRESS,"_onSubagentProgress"],[SSE_EVENTS.SUBAGENT_MESSAGE,"_onSubagentMessage"],[SSE_EVENTS.SUBAGENT_TOOL_RESULT,"_onSubagentToolResult"],[SSE_EVENTS.SUBAGENT_COMPLETED,"_onSubagentCompleted"],[SSE_EVENTS.IMAGE_DETECTED,"_onImageDetected"],[SSE_EVENTS.TUNNEL_STARTED,"_onTunnelStarted"],[SSE_EVENTS.TUNNEL_STOPPED,"_onTunnelStopped"],[SSE_EVENTS.TUNNEL_PROGRESS,"_onTunnelProgress"],[SSE_EVENTS.TUNNEL_ERROR,"_onTunnelError"],[SSE_EVENTS.TUNNEL_QR_ROTATED,"_onTunnelQrRotated"],[SSE_EVENTS.TUNNEL_QR_REGENERATED,"_onTunnelQrRegenerated"],[SSE_EVENTS.TUNNEL_QR_AUTH_USED,"_onTunnelQrAuthUsed"],[SSE_EVENTS.PLAN_SUBAGENT,"_onPlanSubagent"],[SSE_EVENTS.PLAN_PROGRESS,"_onPlanProgress"],[SSE_EVENTS.PLAN_STARTED,"_onPlanStarted"],[SSE_EVENTS.PLAN_CANCELLED,"_onPlanCancelled"],[SSE_EVENTS.PLAN_COMPLETED,"_onPlanCompleted"],[SSE_EVENTS.ORCHESTRATOR_STATE_CHANGED,"_onOrchestratorStateChanged"],[SSE_EVENTS.ORCHESTRATOR_PLAN_PROGRESS,"_onOrchestratorPlanProgress"],[SSE_EVENTS.ORCHESTRATOR_PLAN_READY,"_onOrchestratorPlanReady"],[SSE_EVENTS.ORCHESTRATOR_PHASE_STARTED,"_onOrchestratorPhaseStarted"],[SSE_EVENTS.ORCHESTRATOR_PHASE_COMPLETED,"_onOrchestratorPhaseCompleted"],[SSE_EVENTS.ORCHESTRATOR_PHASE_FAILED,"_onOrchestratorPhaseFailed"],[SSE_EVENTS.ORCHESTRATOR_VERIFICATION,"_onOrchestratorVerification"],[SSE_EVENTS.ORCHESTRATOR_TASK_ASSIGNED,"_onOrchestratorTaskAssigned"],[SSE_EVENTS.ORCHESTRATOR_TASK_COMPLETED,"_onOrchestratorTaskCompleted"],[SSE_EVENTS.ORCHESTRATOR_TASK_FAILED,"_onOrchestratorTaskFailed"],[SSE_EVENTS.ORCHESTRATOR_COMPLETED,"_onOrchestratorCompleted"],[SSE_EVENTS.ORCHESTRATOR_ERROR,"_onOrchestratorError"],[SSE_EVENTS.CLIPBOARD_WRITE,"_onClipboardWrite"]];function parseSessionPrefix(c){if(!c)return null;const e=c.match(/^(w\d+-[a-zA-Z0-9_-]+|s\d+-[a-zA-Z0-9_-]+)/);if(!e)return null;const t=e[1],s=c.slice(t.length);return s===""?{prefix:t,suffix:""}:s.startsWith(": ")?{prefix:t,suffix:s.slice(2)}:null}class CodemanApp{constructor(){this.sessions=new Map,this._shortIdCache=new Map,this.sessionOrder=[],this.draggedTabId=null,this.cases=[],this.currentRun=null,this.totalTokens=0,this.globalStats=null,this.eventSource=null,this._clientId=typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"c-"+Math.random().toString(36).slice(2)+Date.now().toString(36),this.terminal=null,this.fitAddon=null,this.activeSessionId=null,this.soloSessionId=this._detectSoloSessionId(),this.isSoloWindow=!!this.soloSessionId,this.detachedSessions=new Set,this.detachedWindows=new Map,this._detachWatchTimers=new Map,this.windowChannel=null,this._redockGrace=new Map,this._detachPingPending=null,this._detachLivenessTimer=null,this._detachOrphanStrikes=new Map,this._initGeneration=0,this._initFallbackTimer=null,this._selectGeneration=0,this.terminalLoadStates=new Map,this.respawnStatus={},this.respawnTimers={},this.respawnCountdownTimers={},this.respawnActionLogs={},this.timerCountdownInterval=null,this.terminalBuffers=new Map,this.editingSessionId=null,this.pendingCloseSessionId=null,this.muxSessions=[],this.ralphStates=new Map,this.subagents=new Map,this.subagentActivity=new Map,this.subagentToolResults=new Map,this.activeSubagentId=null,this.subagentPanelVisible=!1,this.subagentWindows=new Map,this.subagentWindowZIndex=ZINDEX_SUBAGENT_BASE,this.minimizedSubagents=new Map,this._subagentHideTimeout=null,this.subagentParentMap=new Map,this.teams=new Map,this.teamTasks=new Map,this.teammateMap=new Map,this.teammatePanesByName=new Map,this.teammateTerminals=new Map,this.terminalBufferCache=new Map,this.ralphStatePanelCollapsed=!0,this.ralphClosedSessions=new Set,this.planSubagents=new Map,this.planSubagentWindowZIndex=ZINDEX_PLAN_SUBAGENT_BASE,this.planGenerationStopped=!1,this.planAgentsMinimized=!1,this.wizardDragState=null,this.wizardDragListeners=null,this.wizardPosition=null,this.projectInsights=new Map,this.logViewerWindows=new Map,this.logViewerWindowZIndex=ZINDEX_LOG_VIEWER_BASE,this.projectInsightsPanelVisible=!1,this.orchestratorState=null,this.orchestratorPanelVisible=!1,this.currentSessionWorkingDir=null,this.imagePopups=new Map,this.imagePopupZIndex=ZINDEX_IMAGE_POPUP_BASE,this.fileBrowserData=null,this.fileBrowserExpandedDirs=new Set,this.fileBrowserFilter="",this.fileBrowserAllExpanded=!1,this.fileBrowserDragListeners=null,this.filePreviewContent="",this._toastContainer=null,this._tunnelUrl=null,this.tabAlerts=new Map,this.pendingHooks=new Map,this._ws=null,this._wsSessionId=null,this._wsReady=!1,this.pendingWrites=[],this.writeFrameScheduled=!1,this._wasAtBottomBeforeWrite=!0,this.syncWaitTimeout=null,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._bufferLoadSeq=0,this._bufferLoadOwner=null,this.flickerFilterBuffer="",this.flickerFilterActive=!1,this.flickerFilterTimeout=null,this._debounceTimers=Object.create(null),this.systemStatsInterval=null,this.sseReconnectTimeout=null,this._sseListenerCleanup=null,this.reconnectAttempts=0,this.maxReconnectAttempts=10,this.isOnline=navigator.onLine,this._inputQueue=new Map,this._inputQueueMaxBytes=64*1024,this._connectionStatus="connected",this._inputSendChain=Promise.resolve(),this._localEchoOverlay=null,this._localEchoEnabled=!1,this._restoringFlushedState=!1,this.activeFocusTrap=null,this.notificationManager=new NotificationManager(this),this.idleTimers=new Map,this._elemCache={},this.init()}$(e){return this._elemCache[e]||(this._elemCache[e]=document.getElementById(e)),this._elemCache[e]}_clearTimer(e){this[e]&&(clearTimeout(this[e]),this[e]=null)}_isStaleSelect(e){return e!==this._selectGeneration?(this._isLoadingBuffer&&this._finishBufferLoad(e),this._restoringFlushedState=!1,!0):!1}formatTokens(e){if(e>=1e6){const t=e/1e6;return t>=10?`${t.toFixed(1)}m`:`${t.toFixed(2)}m`}else if(e>=1e3){const t=e/1e3;return t>=100?`${t.toFixed(0)}k`:`${t.toFixed(1)}k`}return String(e)}estimateCost(e,t){const s=e/1e6*15,i=t/1e6*75;return s+i}setPendingHook(e,t){this.pendingHooks.has(e)||this.pendingHooks.set(e,new Set),this.pendingHooks.get(e).add(t),this.updateTabAlertFromHooks(e)}clearPendingHooks(e,t=null){const s=this.pendingHooks.get(e);s&&(t?s.delete(t):s.clear(),s.size===0&&this.pendingHooks.delete(e),this.updateTabAlertFromHooks(e))}updateTabAlertFromHooks(e){const t=this.pendingHooks.get(e);!t||t.size===0?this.tabAlerts.delete(e):t.has("permission_prompt")||t.has("elicitation_dialog")?this.tabAlerts.set(e,"action"):t.has("idle_prompt")&&this.tabAlerts.set(e,"idle"),this.renderSessionTabs()}init(){MobileDetection.init(),this._initWindowChannel(),this.isSoloWindow&&document.body.classList.add("solo-mode"),KeyboardHandler.init(),SwipeHandler.init(),VoiceInput.init(),KeyboardAccessoryBar.init(),this.loadAppSettingsFromStorage().extendedKeyboardBar&&KeyboardAccessoryBar.setMode("extended"),this.applyHeaderVisibilitySettings(),this.applyTabWrapSettings(),this.applyMonitorVisibility(),document.documentElement.classList.remove("mobile-init"),requestAnimationFrame(()=>{this.initTerminal(),this.loadFontSize(),this.connectSSE(),this._initFallbackTimer=setTimeout(()=>{this._initGeneration===0&&this.loadState()},3e3)}),this.registerServiceWorker(),this.loadTunnelStatus();const t=fetch("/api/settings").then(s=>s.ok?s.json():null).then(s=>s?.data??null).catch(()=>null);if(this.loadQuickStartCases(null,t),this._initRunMode(),this.setupEventListeners(),MobileDetection.isTouchDevice()){const s=i=>{i&&i.addEventListener("touchstart",n=>{if(!KeyboardHandler.keyboardVisible)return;const o=n.target.closest("button");o&&(n.preventDefault(),o.click(),typeof app<"u"&&app.terminal&&app.terminal.focus())},{passive:!1})};s(document.querySelector(".toolbar")),s(document.querySelector(".welcome-overlay"))}this.setupOnlineDetection(),this.loadAppSettingsFromServer(t).then(()=>{this.applyHeaderVisibilitySettings(),this.applyTabWrapSettings(),this.applyMonitorVisibility()}),document.body.classList.add("app-loaded")}_initWebGL(){if(!(typeof WebglAddon>"u"))try{this._webglAddon=new WebglAddon.WebglAddon,this._webglAddon.onContextLoss(()=>{console.error("[CRASH-DIAG] WebGL context LOST \u2014 falling back to canvas renderer"),_crashDiag.log("WEBGL_LOST"),this._disableWebGLSticky("context-lost"),this._disposeWebGLObserver(),this._webglAddon?.dispose(),this._webglAddon=null,this._scheduleTerminalRepaint()}),this.terminal.loadAddon(this._webglAddon),console.log("[CRASH-DIAG] WebGL renderer enabled"),this._installWebGLLongTaskGuard()}catch{}}_installWebGLLongTaskGuard(){if(typeof PerformanceObserver>"u"||this._webglLongTaskObserver)return;const e=performance.now(),t=[];try{this._webglLongTaskObserver=new PerformanceObserver(s=>{if(!this._webglAddon)return;const i=performance.now();i-e<WEBGL_FALLBACK.GRACE_MS||evaluateWebGLLongTaskTrip(t,s.getEntries(),i)&&(console.warn(`[CRASH-DIAG] WebGL long-task threshold (${t.length} stalls/${WEBGL_FALLBACK.WINDOW_MS}ms) \u2014 falling back to canvas renderer`),_crashDiag.log(`WEBGL_FALLBACK: ${t.length}`),this._disableWebGLSticky("long-tasks"),this._disposeWebGLObserver(),this._webglAddon?.dispose(),this._webglAddon=null,this._scheduleTerminalRepaint())}),this._webglLongTaskObserver.observe({type:"longtask",buffered:!1})}catch{}}_disposeWebGLObserver(){if(this._webglLongTaskObserver){try{this._webglLongTaskObserver.disconnect()}catch{}this._webglLongTaskObserver=null}}_scheduleTerminalRepaint(){if(this._terminalRepaintScheduled)return;this._terminalRepaintScheduled=!0,(typeof requestAnimationFrame=="function"?requestAnimationFrame:t=>setTimeout(t,0))(()=>{this._terminalRepaintScheduled=!1;try{this.terminal?.refresh(0,this.terminal.rows-1)}catch{}})}_disableWebGLSticky(e){try{localStorage.setItem("codeman-webgl-disabled",JSON.stringify({reason:e,at:Date.now()}))}catch{}}setupEventListeners(){const e=[{key:"?",altKey:"/",ctrl:!0,action:()=>this.showHelp()},{key:"w",ctrl:!0,action:()=>this.killActiveSession()},{key:"Tab",ctrl:!0,action:()=>this.nextSession()},{key:"l",ctrl:!0,action:()=>this.clearTerminal()},{key:"R",ctrl:!0,shift:!0,action:()=>this.restoreTerminalSize()},{key:"=",altKey:"+",ctrl:!0,action:()=>this.increaseFontSize()},{key:"-",ctrl:!0,action:()=>this.decreaseFontSize()},{key:"V",ctrl:!0,shift:!0,action:()=>VoiceInput.toggle()},{key:"{",ctrl:!0,shift:!0,action:()=>this.moveActiveTabLeft()},{key:"}",ctrl:!0,shift:!0,action:()=>this.moveActiveTabRight()}];document.addEventListener("keydown",s=>{if(!(s.isComposing||s.keyCode===229)){if(s.key==="Escape"&&(this.closeAllPanels(),this.closeHelp()),s.altKey&&!s.ctrlKey&&!s.shiftKey&&s.key>="1"&&s.key<="9"){const i=parseInt(s.key)-1;i<this.sessionOrder.length&&(s.preventDefault(),this.selectSession(this.sessionOrder[i]));return}for(const i of e){const n=s.key===i.key||i.altKey&&s.key===i.altKey,o=i.ctrl?s.ctrlKey||s.metaKey:!0,a=i.shift?s.shiftKey:!s.shiftKey;if(n&&o&&a){s.preventDefault(),i.action();return}}}},!0);const t=this.$("headerTokens");t&&!t._statsHandlerAttached&&(t.classList.add("clickable"),t._statsHandlerAttached=!0,t.addEventListener("click",()=>this.openTokenStats())),this.setupColorPicker()}_updateSseSubscription(e){try{const t=JSON.stringify({clientId:this._clientId,sessions:e?[e]:null});fetch("/api/events/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:t,keepalive:!0}).catch(()=>{})}catch{}}_detectSoloSessionId(){try{if(typeof window<"u"&&typeof window.__CODEMAN_SOLO__=="string"&&window.__CODEMAN_SOLO__)return window.__CODEMAN_SOLO__;const e=location.pathname.match(/^\/session\/([^/]+)\/?$/);return e?decodeURIComponent(e[1]):null}catch{return null}}detachSession(e){if(this.isSoloWindow||!this.sessions.has(e)||this.detachedSessions.has(e)&&this._raiseDetached(e))return;const t="width=960,height=680,menubar=no,toolbar=no,location=no,status=no";let s=null;try{s=window.open("/session/"+encodeURIComponent(e),"codeman-session-"+e,t)}catch{}if(!s){this.showToast?.("Pop-out blocked \u2014 allow popups for this site to detach a session","error");return}this.detachedWindows.set(e,s),this._markDetached(e,!0),this._watchDetachedWindow(e,s),this._postWindowMessage({type:"detached",id:e});try{s.focus()}catch{}}_raiseDetached(e){const t=this.detachedWindows.get(e);if(t&&!t.closed){try{t.focus()}catch{}return!0}return t&&t.closed?(this._redock(e),!1):(this._postWindowMessage({type:"focus-request",id:e}),!0)}redockSession(e){const t=this.detachedWindows.get(e);if(t&&!t.closed)try{t.close()}catch{}this._postWindowMessage({type:"close-request",id:e}),this._redock(e)}_redock(e){const t=this._detachWatchTimers.get(e);t&&(clearInterval(t),this._detachWatchTimers.delete(e)),this._cancelPendingRedock(e),this._detachOrphanStrikes.delete(e),this.detachedWindows.delete(e),this._markDetached(e,!1)}_scheduleRedock(e){if(this._redockGrace.has(e))return;const t=setTimeout(()=>{this._redockGrace.delete(e),this._redock(e)},1500);this._redockGrace.set(e,t)}_cancelPendingRedock(e){const t=this._redockGrace.get(e);t&&(clearTimeout(t),this._redockGrace.delete(e))}_markDetached(e,t){t?this.detachedSessions.add(e):this.detachedSessions.delete(e);const s=this.$("sessionTabs"),i=s&&s.querySelector(`.session-tab[data-id="${e}"]`);i&&i.classList.toggle("detached",t)}_watchDetachedWindow(e,t){const s=this._detachWatchTimers.get(e);s&&clearInterval(s);const i=setInterval(()=>{(!t||t.closed)&&(clearInterval(i),this._detachWatchTimers.delete(e),this._redock(e))},800);this._detachWatchTimers.set(e,i)}_initWindowChannel(){if(!(typeof BroadcastChannel>"u")){try{this.windowChannel=new BroadcastChannel("codeman-windows")}catch{this.windowChannel=null;return}if(this.windowChannel.onmessage=e=>this._onWindowMessage(e.data),this.isSoloWindow){this._postWindowMessage({type:"detached",id:this.soloSessionId});const e=()=>this._postWindowMessage({type:"redocked",id:this.soloSessionId});window.addEventListener("pagehide",e),window.addEventListener("beforeunload",e)}else this._postWindowMessage({type:"roll-call"}),this._startDetachLiveness()}}_postWindowMessage(e){try{this.windowChannel&&this.windowChannel.postMessage(e)}catch{}}_onWindowMessage(e){if(!(!e||typeof e!="object")){if(this.isSoloWindow){if(e.type==="roll-call"){this._postWindowMessage({type:"detached",id:this.soloSessionId});return}if(e.id!==this.soloSessionId)return;if(e.type==="close-request")try{window.close()}catch{}else if(e.type==="focus-request")try{window.focus()}catch{}return}e.type==="detached"&&e.id?(this._cancelPendingRedock(e.id),this._detachPingPending?.delete(e.id),this._detachOrphanStrikes.delete(e.id),this._markDetached(e.id,!0)):e.type==="redocked"&&e.id?this._scheduleRedock(e.id):e.type==="detach-request"&&e.id&&this.detachSession(e.id)}}_startDetachLiveness(){this._detachLivenessTimer||(this._detachLivenessTimer=setInterval(()=>this._pingDetached(),5e3))}_pingDetached(){const e=[];for(const t of this.detachedSessions){const s=this.detachedWindows.get(t);s?s.closed&&this._redock(t):e.push(t)}e.length&&(this._detachPingPending=new Set(e),this._postWindowMessage({type:"roll-call"}),setTimeout(()=>{if(this._detachPingPending){for(const t of this._detachPingPending){const s=(this._detachOrphanStrikes.get(t)||0)+1;s>=2?(this._detachOrphanStrikes.delete(t),this._redock(t)):this._detachOrphanStrikes.set(t,s)}this._detachPingPending=null}},1200))}_applySoloMode(){document.body.classList.add("solo-mode");const e=this.sessions.get(this.soloSessionId);if(!e){this._showSoloSessionGone();return}this.activeSessionId=null,this.selectSession(this.soloSessionId);const t=this.getSessionName(e)||"Session",s=document.getElementById("soloSessionTitle");s&&(s.textContent=t,s.style.display="");const i=document.getElementById("soloRedockBtn");i&&(i.style.display=""),document.title=t+" \u2014 Codeman",this.notificationManager&&(this.notificationManager.originalTitle=document.title);const n=document.querySelector(".header-brand .logo");n&&(n.onclick=o=>{o.preventDefault()})}_showSoloSessionGone(){if(document.body.classList.add("solo-mode"),document.querySelector(".solo-gone-overlay"))return;const e=document.createElement("div");e.className="solo-gone-overlay",e.innerHTML='<h2>Session unavailable</h2><p>This session has ended or is no longer available.</p><button class="btn-primary" onclick="window.close()">Close window</button>',document.body.appendChild(e),document.title="Session ended \u2014 Codeman"}connectSSE(){if(!navigator.onLine){this.setConnectionStatus("offline");return}this._clearTimer("sseReconnectTimeout"),this._sseListenerCleanup&&(this._sseListenerCleanup(),this._sseListenerCleanup=null),this.eventSource&&(this.eventSource.close(),this.eventSource=null),this.reconnectAttempts===0?this.setConnectionStatus("connecting"):this.setConnectionStatus("reconnecting");const e=new URLSearchParams({clientId:this._clientId});this.activeSessionId&&e.set("sessions",this.activeSessionId),this.eventSource=new EventSource(`/api/events?${e.toString()}`);const t=[],s=(i,n)=>{this.eventSource.addEventListener(i,n),t.push({event:i,handler:n})};if(this._sseListenerCleanup=()=>{for(const{event:i,handler:n}of t)this.eventSource&&this.eventSource.removeEventListener(i,n);t.length=0},this.eventSource.onopen=()=>{this.reconnectAttempts=0,this.setConnectionStatus("connected")},this.eventSource.onerror=()=>{this.reconnectAttempts++,this.reconnectAttempts>=this.maxReconnectAttempts?this.setConnectionStatus("disconnected"):this.setConnectionStatus("reconnecting"),this.eventSource&&(this.eventSource.close(),this.eventSource=null),this._clearTimer("sseReconnectTimeout");const i=this.reconnectAttempts<=1?200:Math.min(500*Math.pow(2,this.reconnectAttempts-2),3e4);this.sseReconnectTimeout=setTimeout(()=>this.connectSSE(),i)},!this._sseHandlerWrappers){this._sseHandlerWrappers=new Map;for(const[i,n]of _SSE_HANDLER_MAP){const o=this[n];this._sseHandlerWrappers.set(i,a=>{try{o.call(this,a.data?JSON.parse(a.data):{})}catch(r){console.error(`[SSE] Error handling ${i}:`,r)}})}}for(const[i]of _SSE_HANDLER_MAP)s(i,this._sseHandlerWrappers.get(i))}_onInit(e){_crashDiag.log(`INIT: ${e.sessions?.length||0} sessions`),this.handleInit(e)}_onSessionCreated(e){this.sessions.set(e.id,e),this.sessionOrder.includes(e.id)||(this.sessionOrder.push(e.id),this.saveSessionOrder()),this.renderSessionTabs(),this.updateCost(),this.sessions.size===1&&this.startSystemStatsPolling()}_onSessionUpdated(e){const t=e.session||e,s=this.sessions.get(t.id),i=t.claudeSessionId&&(!s||!s.claudeSessionId);this.sessions.set(t.id,t),this.renderSessionTabs(),this.updateCost(),t.id===this.activeSessionId&&t.tokens&&this.updateRespawnTokens(t.tokens),this.updateSubagentParentNames(t.id),i&&(this.recheckOrphanSubagents(),requestAnimationFrame(()=>{this.updateConnectionLines()}))}_onSessionDeleted(e){if(this._wsSessionId===e.id&&this._disconnectWs(),this.isSoloWindow&&e.id===this.soloSessionId&&this._showSoloSessionGone(),this.detachedSessions.has(e.id)&&this._redock(e.id),this._cleanupSessionData(e.id),this.activeSessionId===e.id){this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.terminal.clear(),this.showWelcome()}this.renderSessionTabs(),this.renderRalphStatePanel(),this.renderProjectInsightsPanel(),this.sessions.size===0&&this.stopSystemStatsPolling()}_onSSETerminal(e){this._wsReady&&this._wsSessionId===e.id||this._onSessionTerminal(e)}_onSSENeedsRefresh(e){this._wsReady&&this._wsSessionId===e?.id||this._onSessionNeedsRefresh(e)}_onSSEClearTerminal(e){this._wsReady&&this._wsSessionId===e?.id||this._onSessionClearTerminal(e)}_onSessionTerminal(e){if(e.id===this.activeSessionId){if(e.data.length>32768&&_crashDiag.log(`TERMINAL: ${(e.data.length/1024).toFixed(0)}KB`),(this.pendingWrites?.reduce((s,i)=>s+i.length,0)||0)+(this.flickerFilterBuffer?.length||0)>131072){this._clientDropRecoveryTimer||(this._clientDropRecoveryTimer=setTimeout(()=>{this._clientDropRecoveryTimer=null,this._onSessionNeedsRefresh()},2e3));return}this.batchTerminalWrite(e.data)}}_sanitizeHtml(e){const t=document.createElement("template");t.innerHTML=e;const s=t.content;for(const n of s.querySelectorAll("script, iframe, object, embed, form, base, meta, link, style"))n.remove();for(const n of s.querySelectorAll("*"))for(const o of[...n.attributes]){const a=o.name.toLowerCase();if(a.startsWith("on"))n.removeAttribute(o.name);else if(["href","src","action","xlink:href","formaction"].includes(a)){const r=o.value.replace(/\s/g,"").toLowerCase();(r.startsWith("javascript:")||r.startsWith("vbscript:")||r.startsWith("data:text/html"))&&n.removeAttribute(o.name)}}const i=document.createElement("div");return i.appendChild(s),i.innerHTML}_cleanTerminalBuffer(e){const t=e.replace(/\x1b\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x7E]/g,"").replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g,"").replace(/\x1b[PX^_][^\x1b]*\x1b\\/g,"").replace(/\x1b[NO()][A-Z0-9]?/g,"").replace(/\x1b[>=<78cDEHM]/g,"").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g,"").replace(/\r\n/g,`
|
|
8
|
+
`).replace(/\r/g,`
|
|
9
|
+
`),s=[/^\s*❯\s*/,/^\s*[⏵⏺⏸⏹]+\s*/,/^\s*✻\s*(Crunching|Crunched|Thinking)/i,/bypass permissions/i,/\bshift\+tab to cycle\b/i,/^\s*focus\s*$/,/^\s*new task\?/i,/\/clear to save/i,/^\s*─{5,}\s*$/,/\[(Opus|Sonnet|Haiku|GPT|Claude)[\s\S]*(tokens?|\$|¥|%|↑|↓)/i,/^\s*\[\d+[km]?\/\d+[km]?\]/i,/[█░▓▒]{3,}/,/^\s*\(.*\s*(tokens?|context).*\)\s*$/i];return t.split(`
|
|
10
|
+
`).filter(o=>o.trim()?!s.some(r=>r.test(o)):!0).join(`
|
|
11
|
+
`).replace(/[ \t]+$/gm,"").replace(/\n{4,}/g,`
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
`).trim()}_preprocessAsciiArt(e){const t=/[─-╿▀-▟]/,s=/```[\s\S]*?```/g,i=[];return e.replace(s,a=>(i.push(a),`__CODEMAN_FENCE_${i.length-1}__`)).split(/(\n{2,})/).map(a=>/^\n{2,}$/.test(a)||!a.trim()||a.includes("__CODEMAN_FENCE_")?a:t.test(a)?"\n```\n"+a+"\n```\n":a).join("").replace(/__CODEMAN_FENCE_(\d+)__/g,(a,r)=>i[Number(r)])}_renderMarkdown(e){const t=e||"";if(typeof marked<"u"&&marked.parse)try{const i=this._preprocessAsciiArt(t);let n=this._sanitizeHtml(marked.parse(i,{breaks:!0,gfm:!0}));n=n.replace(/<table>/g,'<div class="rv-table-wrap"><table>').replace(/<\/table>/g,"</table></div>");const o=/[─-╿▀-▟]/,a=document.createElement("template");return a.innerHTML=n,a.content.querySelectorAll("pre > code").forEach(r=>{const l=r.parentElement,h=o.test(r.textContent||""),d=document.createElement("div");d.className=h?"rv-code-wrap rv-diagram-wrap":"rv-code-wrap";const p=document.createElement("div");p.className="rv-code-actions";const f=document.createElement("button");if(f.className="rv-copy-btn",f.type="button",f.setAttribute("aria-label","Copy code"),f.setAttribute("title","Copy code"),p.appendChild(f),h){l.classList.add("rv-diagram");const E=document.createElement("button");E.className="rv-wrap-toggle",E.type="button",E.setAttribute("aria-label","Toggle line wrapping"),E.setAttribute("title","Toggle line wrapping"),p.appendChild(E)}l.parentNode.insertBefore(d,l),d.appendChild(p),d.appendChild(l)}),a.innerHTML}catch{}return`<pre style="white-space:pre-wrap;word-break:break-word">${t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}</pre>`}_bindResponseViewerInteractions(e){!e||e.dataset.rvBound==="1"||(e.dataset.rvBound="1",e.addEventListener("click",async t=>{const s=t.target.closest(".rv-copy-btn");if(s){t.preventDefault(),t.stopPropagation();const r=s.closest(".rv-code-wrap")?.querySelector("pre code"),l=r?await this._copyText(r.textContent||""):!1;s.classList.remove("rv-copied","rv-copy-failed"),s.classList.add(l?"rv-copied":"rv-copy-failed"),clearTimeout(s._resetTimer),s._resetTimer=setTimeout(()=>{s.classList.remove("rv-copied","rv-copy-failed")},1500);return}const i=t.target.closest(".rv-wrap-toggle");if(!i)return;t.preventDefault(),t.stopPropagation();const n=i.closest(".rv-diagram-wrap"),o=n?.querySelector("pre.rv-diagram");if(!o||!n)return;const a=o.classList.toggle("rv-nowrap");n.classList.toggle("rv-wrap-nowrap",a)}))}async _copyText(e){if(!e)return!1;try{if(navigator.clipboard?.writeText)return await navigator.clipboard.writeText(e),!0}catch{}try{const t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.cssText="position:fixed;top:0;left:0;opacity:0;pointer-events:none",document.body.appendChild(t),t.select();const s=document.execCommand("copy");return document.body.removeChild(t),s}catch{return!1}}async toggleResponseViewer(){const e=document.getElementById("responseViewer"),t=document.getElementById("responseViewerBackdrop");if(!e)return;if(e.classList.contains("visible")){e.classList.remove("visible"),t.classList.remove("visible");return}if(this.activeSessionId)try{let o=((await(await fetch(`/api/sessions/${this.activeSessionId}/last-response`)).json())?.data??{}).text||"";if(!o){const d=(await(await fetch(`/api/sessions/${this.activeSessionId}/terminal`)).json())?.data??{};d.terminalBuffer&&(o=this._cleanTerminalBuffer(d.terminalBuffer))}const a=document.getElementById("responseViewerBody");a.innerHTML=this._renderMarkdown(o),this._bindResponseViewerInteractions(a);const r=document.getElementById("responseViewerTitle"),l=document.getElementById("responseViewerMore");r&&(r.textContent="Last Response"),l&&(l.style.display="",l.textContent="More"),e.classList.add("visible"),t.classList.add("visible"),a.scrollTop=0}catch(i){console.error("Failed to load response:",i)}}async loadFullContext(){if(!this.activeSessionId)return;const e=document.getElementById("responseViewerMore");e&&(e.textContent="...");try{const i=((await(await fetch(`/api/sessions/${this.activeSessionId}/last-response?context=full`)).json())?.data??{}).messages||[],n=document.getElementById("responseViewerBody"),o=document.getElementById("responseViewerTitle");if(!n)return;if(i.length===0){n.textContent="No conversation history available";return}n.innerHTML="";for(const a of i){const r=document.createElement("div"),l=a.role==="user";r.className="rv-message "+(l?"rv-msg-user":"rv-msg-assistant");const h=document.createElement("div");h.className="rv-role "+(l?"rv-role-user":"rv-role-assistant"),h.textContent=l?"You":"Claude",r.appendChild(h);const d=document.createElement("div");d.className="rv-text",d.innerHTML=this._renderMarkdown(a.text),r.appendChild(d),n.appendChild(r)}this._bindResponseViewerInteractions(n),o&&(o.textContent=`Conversation (${i.length} messages)`),e&&(e.style.display="none"),n.scrollTop=n.scrollHeight}catch(t){console.error("Failed to load context:",t)}finally{e&&(e.textContent="More")}}async _onSessionNeedsRefresh(){if(!(!this.activeSessionId||!this.terminal)&&!this._isLoadingBuffer)try{const t=(await(await fetch(`/api/sessions/${this.activeSessionId}/terminal?tail=${TERMINAL_TAIL_SIZE}`)).json())?.data??{};t.terminalBuffer&&(this.terminal.clear(),this.terminal.reset(),await this.chunkedTerminalWrite(t.terminalBuffer),this.terminal.scrollToBottom(),this._localEchoOverlay?.rerender(),this.activeSessionId&&this.sendResize(this.activeSessionId))}catch(e){console.error("needsRefresh reload failed:",e)}}async _onSessionClearTerminal(e){if(e.id===this.activeSessionId){if(this._isLoadingBuffer)return;try{const s=(await(await fetch(`/api/sessions/${e.id}/terminal`)).json())?.data??{};if(this.terminal.clear(),this.terminal.reset(),s.terminalBuffer){const i=s.terminalBuffer.replace(DEC_SYNC_STRIP_RE,"");await this.chunkedTerminalWrite(i)}this.sendResize(e.id),this._localEchoOverlay?.rerender()}catch(t){console.error("clearTerminal refresh failed:",t)}}}_onSessionCompletion(e){this.totalCost+=e.cost||0,this.updateCost(),e.id===this.activeSessionId&&(this.terminal.writeln(""),this.terminal.writeln(`\x1B[1;32m Done (Cost: $${(e.cost||0).toFixed(4)})\x1B[0m`))}_onSessionError(e){e.id===this.activeSessionId&&this.terminal.writeln(`\x1B[1;31m Error: ${e.error}\x1B[0m`),this._notifySession(e.id,"critical","session-error","Session Error",e.error||"Unknown error")}_onSessionExit(e){this._wsSessionId===e.id&&this._disconnectWs();const t=this.sessions.get(e.id);t&&(t.status="stopped",this.renderSessionTabs(),e.id===this.activeSessionId&&this._updateLocalEchoState()),e.code&&e.code!==0&&this._notifySession(e.id,"critical","session-crash","Session Crashed",`Exited with code ${e.code}`)}_onSessionIdle(e){const t=this.sessions.get(e.id);if(t&&(t.status="idle",this.renderSessionTabs(),this.sendPendingCtrlL(e.id),e.id===this.activeSessionId&&this._updateLocalEchoState()),!this.respawnStatus[e.id]?.enabled){const s=this.notificationManager?.preferences?.stuckThresholdMs||6e5;clearTimeout(this.idleTimers.get(e.id)),this.idleTimers.set(e.id,setTimeout(()=>{this._notifySession(e.id,"warning","session-stuck","Session Idle",`Idle for ${Math.round(s/6e4)}+ minutes`),this.idleTimers.delete(e.id)},s))}}_onSessionWorking(e){const t=this.sessions.get(e.id);t&&(t.status="busy",this.pendingHooks.has(e.id)||this.tabAlerts.delete(e.id),this.renderSessionTabs(),this.sendPendingCtrlL(e.id),e.id===this.activeSessionId&&this._updateLocalEchoState());const s=this.idleTimers.get(e.id);s&&(clearTimeout(s),this.idleTimers.delete(e.id))}_onSessionAutoClear(e){e.sessionId===this.activeSessionId&&(this.showToast(`Auto-cleared at ${e.tokens.toLocaleString()} tokens`,"info"),this.updateRespawnTokens(0)),this._notifySession(e.sessionId,"info","auto-clear","Auto-Cleared",`Context reset at ${(e.tokens||0).toLocaleString()} tokens`)}_onSessionLimitPauseScheduled(e){const t=this.sessions.get(e.sessionId);t&&(t.autoResumeAt=e.resumeAt);const s=new Date(e.resumeAt).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});e.sessionId===this.activeSessionId&&this.showToast(`Usage limit reached \u2014 auto-resume at ${s}`,"warning"),this._notifySession(e.sessionId,"warning","limit-pause","Usage Limit Reached",`Auto-resume scheduled for ${s}`),this.updateAutoResumeStatus(e.sessionId)}_onSessionLimitResume(e){const t=this.sessions.get(e.sessionId);t&&(t.autoResumeAt=void 0),e.sessionId===this.activeSessionId&&this.showToast("Usage limit reset \u2014 work resumed automatically","success"),this._notifySession(e.sessionId,"info","limit-resume","Auto-Resumed","Usage limit reset \u2014 continuing work"),this.updateAutoResumeStatus(e.sessionId)}_onSessionLimitResumeCancelled(e){const t=this.sessions.get(e.sessionId);t&&(t.autoResumeAt=void 0),this.updateAutoResumeStatus(e.sessionId)}_onSessionCliInfo(e){const t=this.sessions.get(e.sessionId);t&&(e.version&&(t.cliVersion=e.version),e.model&&(t.cliModel=e.model),e.accountType&&(t.cliAccountType=e.accountType),e.latestVersion&&(t.cliLatestVersion=e.latestVersion)),e.sessionId===this.activeSessionId&&this.updateCliInfoDisplay()}_onScheduledCreated(e){this.currentRun=e,this.showTimer()}_onScheduledUpdated(e){this.currentRun=e,this.updateTimer()}_onScheduledCompleted(e){this.currentRun=e,this.hideTimer(),this.showToast("Scheduled run completed!","success")}_onScheduledStopped(){this.currentRun=null,this.hideTimer()}setConnectionStatus(e){this._connectionStatus=e,this._updateConnectionIndicator(),e==="connected"&&this._inputQueue.size>0&&this._drainInputQueues()}_connectWs(e){this._disconnectWs();const s=`${location.protocol==="https:"?"wss:":"ws:"}//${location.host}/ws/sessions/${e}/terminal`,i=new WebSocket(s);this._ws=i,this._wsSessionId=e,i.onopen=()=>{this._ws===i&&(this._wsReady=!0,this._wsReconnectAttempts=0,this.sendResize(e)?.catch?.(()=>{}),this._startMobileResizeRetry(e))},i.onmessage=n=>{if(this._ws===i)try{const o=JSON.parse(n.data);o.t==="o"?this._onSessionTerminal({id:e,data:o.d}):o.t==="c"?this._onSessionClearTerminal({id:e}):o.t==="r"&&this._onSessionNeedsRefresh({id:e})}catch{}},i.onclose=n=>{if(this._ws===i&&(this._ws=null,this._wsSessionId=null,this._wsReady=!1,this._stopMobileResizeRetry(),n.code<4004&&this.activeSessionId===e)){const o=Math.min(1e3*Math.pow(2,this._wsReconnectAttempts||0),1e4);this._wsReconnectAttempts=(this._wsReconnectAttempts||0)+1,this._wsReconnectTimer=setTimeout(()=>{this._wsReconnectTimer=null,this.activeSessionId===e&&this._connectWs(e)},o)}},i.onerror=()=>{}}_disconnectWs(){this._clearTimer("_wsReconnectTimer"),this._wsReconnectAttempts=0,this._stopMobileResizeRetry(),this._ws&&(this._ws.onclose=null,this._ws.close(),this._ws=null,this._wsSessionId=null,this._wsReady=!1)}_startMobileResizeRetry(e){this._stopMobileResizeRetry(),(typeof MobileDetection<"u"&&MobileDetection.getDeviceType?MobileDetection.getDeviceType():"desktop")!=="desktop"&&(this._mobileResizeRetryTimer=setInterval(()=>{document.visibilityState==="visible"&&(!this._wsReady||this._wsSessionId!==e||typeof KeyboardHandler<"u"&&KeyboardHandler.keyboardVisible||this.sendResize(e)?.catch?.(()=>{}))},MOBILE_RESIZE_RETRY_MS))}_stopMobileResizeRetry(){this._mobileResizeRetryTimer&&(clearInterval(this._mobileResizeRetryTimer),this._mobileResizeRetryTimer=null)}_sendInputAsync(e,t){if(!this.isOnline||this._connectionStatus==="disconnected"){this._enqueueInput(e,t);return}if(this._wsReady&&this._wsSessionId===e)try{this._ws.send(JSON.stringify({t:"i",d:t})),this.clearPendingHooks(e);return}catch{}this._inputSendChain=this._inputSendChain.then(()=>{fetch(`/api/sessions/${e}/input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input:t}),keepalive:t.length<65536}).then(i=>{i.ok?this.clearPendingHooks(e):this._enqueueInput(e,t)}).catch(()=>{this._enqueueInput(e,t)})})}_enqueueInput(e,t){let i=(this._inputQueue.get(e)||"")+t;i.length>this._inputQueueMaxBytes&&(i=i.slice(i.length-this._inputQueueMaxBytes)),this._inputQueue.set(e,i),this._updateConnectionIndicator()}async _drainInputQueues(){if(this._inputQueue.size===0)return;const e=new Map(this._inputQueue);this._inputQueue.clear(),this._updateConnectionIndicator();for(const[t,s]of e)(await this._apiPost(`/api/sessions/${t}/input`,{input:s}))?.ok||this._enqueueInput(t,s);this._updateConnectionIndicator()}_updateConnectionIndicator(){const e=this.$("connectionIndicator"),t=this.$("connectionDot"),s=this.$("connectionText");if(!e||!t||!s)return;let i=0;for(const r of this._inputQueue.values())i+=r.length;const n=this._connectionStatus,o=i>0;if((n==="connected"||n==="connecting")&&!o){e.style.display="none";return}e.style.display="flex",t.className="connection-dot";const a=r=>r<1024?`${r}B`:`${(r/1024).toFixed(1)}KB`;n==="connected"&&o?(t.classList.add("draining"),s.textContent=`Sending ${a(i)}...`):n==="reconnecting"?(t.classList.add("reconnecting"),s.textContent=o?`Reconnecting (${a(i)} queued)`:"Reconnecting..."):(t.classList.add("offline"),s.textContent=o?`Offline (${a(i)} queued)`:"Offline")}setupOnlineDetection(){window.addEventListener("online",()=>{this.isOnline=!0,this.reconnectAttempts=0,this.connectSSE()}),window.addEventListener("offline",()=>{this.isOnline=!1,this.setConnectionStatus("offline")})}_updateCjkInputState(){const e=document.getElementById("cjkInput");if(!e)return;const t=this.loadAppSettingsFromStorage(),s=this.getDefaultSettings?.()||{},i=this._serverCjkOverride||(t.cjkInputEnabled??s.cjkInputEnabled??!1);e.classList.toggle("cjk-input-visible",!!i),document.body.classList.toggle("cjk-input-visible",!!i),e.style.display=i?"block":"none",e.setAttribute("aria-hidden",i?"false":"true"),i&&e.value==="\u200B"&&(e.value=""),i||(window.cjkActive=!1),typeof KeyboardHandler<"u"&&KeyboardHandler.updateLayoutForKeyboard()}_resetAllAppState(){this.sessions.clear(),this.ralphStates.clear(),this.terminalBuffers.clear(),this.terminalBufferCache.clear(),this.projectInsights.clear(),this.teams.clear(),this.teamTasks.clear();for(const e of this.idleTimers.values())clearTimeout(e);if(this.idleTimers.clear(),this._clearTimer("flickerFilterTimeout"),this.flickerFilterBuffer="",this.flickerFilterActive=!1,this._clearTimer("syncWaitTimeout"),this.pendingWrites=[],this.writeFrameScheduled=!1,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._bufferLoadOwner=null,this._chunkedWriteGen=(this._chunkedWriteGen||0)+1,this._localEchoOverlay?.rerender(),this.pendingHooks.clear(),this._parentNameCache&&this._parentNameCache.clear(),this.subagentActivity.clear(),this.subagentToolResults.clear(),MobileDetection.cleanup(),KeyboardHandler.cleanup(),MobileDetection.init(),KeyboardHandler.init(),this.tabAlerts.clear(),this._shownCompletions&&this._shownCompletions.clear(),this.notificationManager?.titleFlashInterval&&(clearInterval(this.notificationManager.titleFlashInterval),this.notificationManager.titleFlashInterval=null),this.notificationManager?.groupingMap){for(const{timeout:e}of this.notificationManager.groupingMap.values())clearTimeout(e);this.notificationManager.groupingMap.clear()}this.terminalResizeObserver&&(this.terminalResizeObserver.disconnect(),this.terminalResizeObserver=null),this.planLoadingTimer&&(clearInterval(this.planLoadingTimer),this.planLoadingTimer=null),this.timerCountdownInterval&&(clearInterval(this.timerCountdownInterval),this.timerCountdownInterval=null),this.runSummaryAutoRefreshTimer&&(clearInterval(this.runSummaryAutoRefreshTimer),this.runSummaryAutoRefreshTimer=null)}handleInit(e){this._clearTimer("_initFallbackTimer");const t=++this._initGeneration;if(this._serverCjkOverride=e.inputCjkForm||!1,this._updateCjkInputState(),e.version){const n=this.$("versionDisplay"),o=this.$("headerVersion");n&&(n.textContent=`v${e.version}`,n.title=`Codeman v${e.version}`),o&&(o.textContent=`v${e.version}`,o.title=`Codeman v${e.version}`)}VoiceInput.cleanup(),this._resetAllAppState(),e.sessions.forEach(n=>{this.sessions.set(n.id,n),(n.ralphLoop||n.ralphTodos)&&!this.ralphClosedSessions.has(n.id)&&this.ralphStates.set(n.id,{loop:n.ralphLoop||null,todos:n.ralphTodos||[]})});try{localStorage.removeItem("codeman-tab-meta")}catch{}this.syncSessionOrder(),e.respawnStatus?this.respawnStatus=e.respawnStatus:this.respawnStatus={},this.respawnTimers={},this.respawnCountdownTimers={},this.respawnActionLogs={},e.globalStats&&(this.globalStats=e.globalStats),this.totalCost=e.sessions.reduce((n,o)=>n+(o.totalCost||0),0),this.totalCost+=e.scheduledRuns.reduce((n,o)=>n+(o.totalCost||0),0);const s=e.scheduledRuns.find(n=>n.status==="running");if(s&&(this.currentRun=s,this.showTimer()),this.updateCost(),this.renderSessionTabs(),this.sessions.size>0?this.startSystemStatsPolling():this.stopSystemStatsPolling(),this.cleanupAllFloatingWindows(),e.subagents&&(this.subagents.clear(),this.subagentActivity.clear(),this.subagentToolResults.clear(),e.subagents.forEach(n=>{this.subagents.set(n.agentId,n)}),this.renderSubagentPanel(),this.subagentParentMap.clear(),this.loadSubagentParentMap().then(()=>{for(const[n,o]of this.subagentParentMap){const a=this.subagents.get(n);if(a&&this.sessions.has(o)){a.parentSessionId=o;const r=this.sessions.get(o);r&&(a.parentSessionName=this.getSessionName(r)),this.subagents.set(n,a)}}for(const[n]of this.subagents)this.subagentParentMap.has(n)||this.findParentSessionForSubagent(n);this.restoreSubagentWindowStates()})),t!==this._initGeneration)return;if(this.isSoloWindow){this._applySoloMode();return}const i=this.activeSessionId;if(this.activeSessionId=null,this.sessionOrder.length>0){let n=i;if(!n||!this.sessions.has(n))try{n=localStorage.getItem("codeman-active-session")}catch{}n&&this.sessions.has(n)?this.selectSession(n):this.selectSession(this.sessionOrder[0])}}async loadState(){try{const t=await(await fetch("/api/status")).json();this.handleInit(t?.data??{})}catch(e){console.error("Failed to load state:",e)}}_debouncedCall(e,t,s=100){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{this._debounceTimers[e]=null,t.call(this)},s)}renderSessionTabs(){this._inlineRenameActive||this._debouncedCall("sessionTabs",this._renderSessionTabsImmediate)}_updateActiveTabImmediate(e){const t=this.$("sessionTabs");if(!t)return;const s=t.querySelectorAll(".session-tab[data-id]");for(const i of s)i.dataset.id===e?i.classList.add("active"):i.classList.remove("active")}_setTerminalLoadState(e,t,s){this.terminalLoadStates.set(e,{generation:t,phase:s}),this._updateTerminalLoadTab(e)}_clearTerminalLoadState(e,t){const s=this.terminalLoadStates.get(e);s&&s.generation!==t||(this.terminalLoadStates.delete(e),this._updateTerminalLoadTab(e))}_updateTerminalLoadTab(e){const t=this.$("sessionTabs")?.querySelector(`.session-tab[data-id="${e}"]`);if(!t)return;const s=this.terminalLoadStates.get(e);if(t.classList.toggle("tab-loading",!!s),s){if(t.setAttribute("aria-busy","true"),t.dataset.loadPhase=s.phase,!t.querySelector(".tab-load-spinner")){const i=document.createElement("span");i.className="tab-load-spinner",i.setAttribute("aria-hidden","true");const n=t.querySelector(".tab-number");n?n.insertAdjacentElement("afterend",i):t.insertBefore(i,t.firstChild)}}else t.setAttribute("aria-busy","false"),delete t.dataset.loadPhase,t.querySelector(".tab-load-spinner")?.remove()}_renderSessionTabsImmediate(){const e=this.$("sessionTabs"),t=e.querySelectorAll(".session-tab[data-id]"),s=new Set([...t].map(o=>o.dataset.id)),i=new Set(this.sessions.keys());if(s.size===i.size&&[...s].every(o=>i.has(o)))for(const[o,a]of this.sessions){const r=e.querySelector(`.session-tab[data-id="${o}"]`);if(!r)continue;const l=o===this.activeSessionId,h=a.status||"idle",d=this.getSessionName(a),p=a.taskStats||{running:0,total:0},f=p.running>0,E=this.terminalLoadStates.get(o);if(l&&!r.classList.contains("active")?r.classList.add("active"):!l&&r.classList.contains("active")&&r.classList.remove("active"),r.classList.toggle("tab-loading",!!E),E){if(r.setAttribute("aria-busy","true"),r.dataset.loadPhase=E.phase,!r.querySelector(".tab-load-spinner")){const u=document.createElement("span");u.className="tab-load-spinner",u.setAttribute("aria-hidden","true");const T=r.querySelector(".tab-number");T?T.insertAdjacentElement("afterend",u):r.insertBefore(u,r.firstChild)}}else r.setAttribute("aria-busy","false"),delete r.dataset.loadPhase,r.querySelector(".tab-load-spinner")?.remove();const _=this.tabAlerts.get(o),v=_==="action",m=_==="idle",b=r.classList.contains("tab-alert-action"),C=r.classList.contains("tab-alert-idle");if(v&&!b?(r.classList.add("tab-alert-action"),r.classList.remove("tab-alert-idle")):m&&!C?(r.classList.add("tab-alert-idle"),r.classList.remove("tab-alert-action")):!_&&(b||C)&&r.classList.remove("tab-alert-action","tab-alert-idle"),!r.querySelector(".tab-number")){const u=this.sessionOrder.indexOf(o);if(u>=0&&u<9){const T=document.createElement("span");T.className="tab-number",T.textContent=String(u+1),r.insertBefore(T,r.firstChild)}}const S=r.querySelector(".tab-status");S&&!S.classList.contains(h)&&(S.className=`tab-status ${h}`);const w=r.querySelector(".tab-name");if(w&&w.textContent!==d){const u=parseSessionPrefix(d);u&&u.suffix?w.innerHTML='<span class="tab-prefix">'+escapeHtml(u.prefix)+'</span><span class="tab-suffix">: '+escapeHtml(u.suffix)+"</span>":w.textContent=d}const y=r.querySelector(".tab-badge");if(f)if(y)y.textContent!==String(p.running)&&(y.textContent=p.running);else{this._fullRenderSessionTabs();return}else if(y){this._fullRenderSessionTabs();return}const g=r.querySelector(".tab-subagent-badge"),R=this.minimizedSubagents.get(o),A=R?.size||0;if(A>0&&g){const u=g.querySelector(".subagent-label"),T=A===1?"AGENT":`AGENTS (${A})`;u&&u.textContent!==T&&(u.textContent=T);const L=g.querySelector(".subagent-dropdown");if(L){const I=this.renderSubagentTabBadge(o,R),O=document.createElement("div");O.innerHTML=I;const k=O.querySelector(".subagent-dropdown");k&&(L.innerHTML=k.innerHTML)}}else if(A>0&&!g){const u=this.renderSubagentTabBadge(o,R),T=r.querySelector(".tab-gear");T&&T.insertAdjacentHTML("beforebegin",u)}else A===0&&g&&g.remove()}else this._fullRenderSessionTabs()}_fullRenderSessionTabs(){if(this._inlineRenameActive)return;const e=this.$("sessionTabs");document.querySelectorAll("body > .subagent-dropdown").forEach(n=>n.remove()),this.cancelHideSubagentDropdown();const t=[];let s=this.sessionOrder;MobileDetection.getDeviceType()==="mobile"&&this.activeSessionId&&(s=[this.activeSessionId,...this.sessionOrder.filter(n=>n!==this.activeSessionId)]);let i=0;for(const n of s){const o=this.sessions.get(n);if(!o)continue;const a=n===this.activeSessionId,r=o.status||"idle",l=this.getSessionName(o),h=o.mode||"claude",d=o.color||"default",p=o.taskStats||{running:0,total:0},f=p.running>0,E=this.tabAlerts.get(n),_=E==="action"?" tab-alert-action":E==="idle"?" tab-alert-idle":"",v=this.terminalLoadStates.get(n),m=this.minimizedSubagents.get(n),C=(m?.size||0)>0?this.renderSubagentTabBadge(n,m):"",S=o.workingDir&&o.workingDir.split("/").pop()||"",y=(this._tallTabsEnabled??!1)&&o.name&&S&&S!==l;t.push(`<div class="session-tab ${a?"active":""}${_}${v?" tab-loading":""}" data-id="${n}" data-color="${d}" ${v?`data-load-phase="${escapeHtml(v.phase)}"`:""} onclick="app.handleSessionTabClick(event, '${escapeHtml(n)}')" oncontextmenu="event.preventDefault(); app.startInlineRename('${escapeHtml(n)}')" tabindex="0" role="tab" aria-selected="${a?"true":"false"}" aria-busy="${v?"true":"false"}" aria-label="${escapeHtml(l)} session" ${o.workingDir?`title="${escapeHtml(o.workingDir)}"`:""}>
|
|
15
|
+
${i<9?'<span class="tab-number">'+(i+1)+"</span>":""}
|
|
16
|
+
${v?'<span class="tab-load-spinner" aria-hidden="true"></span>':""}
|
|
17
|
+
<span class="tab-status ${r}" aria-hidden="true"></span>
|
|
18
|
+
<span class="tab-info">
|
|
19
|
+
<span class="tab-name-row">
|
|
20
|
+
${h==="shell"?'<span class="tab-mode shell" aria-hidden="true">sh</span>':h==="opencode"?'<span class="tab-mode opencode" aria-hidden="true">oc</span>':h==="codex"?'<span class="tab-mode codex" aria-hidden="true">cx</span>':""}
|
|
21
|
+
<span class="tab-name" data-session-id="${n}">${(()=>{const g=parseSessionPrefix(l);return g&&g.suffix?'<span class="tab-prefix">'+escapeHtml(g.prefix)+'</span><span class="tab-suffix">: '+escapeHtml(g.suffix)+"</span>":escapeHtml(l)})()}</span>
|
|
22
|
+
<span class="tab-detached-badge" aria-hidden="true">detached</span>
|
|
23
|
+
</span>
|
|
24
|
+
${y?`<span class="tab-folder">\u{1F4C1} ${escapeHtml(S)}</span>`:""}
|
|
25
|
+
</span>
|
|
26
|
+
${f?`<span class="tab-badge" onclick="event.stopPropagation(); app.toggleTaskPanel()" aria-label="${p.running} running tasks">${p.running}</span>`:""}
|
|
27
|
+
${C}
|
|
28
|
+
<span class="tab-gear" onclick="event.stopPropagation(); app.openSessionOptions('${escapeHtml(n)}')" title="Session options" aria-label="Session options" tabindex="0">⚙</span>
|
|
29
|
+
<span class="tab-detach" onclick="event.stopPropagation(); app.detachSession('${escapeHtml(n)}')" title="Open in a new window" aria-label="Open session in a new window" tabindex="0">⧉</span>
|
|
30
|
+
<span class="tab-close" onclick="event.stopPropagation(); app.requestCloseSession('${escapeHtml(n)}')" title="Close session" aria-label="Close session" tabindex="0">×</span>
|
|
31
|
+
</div>`),i++}e.innerHTML=t.join(""),this.setupTabDragHandlers(),this.setupTabKeyboardNavigation(e),this.updateConnectionLines()}setupTabKeyboardNavigation(e){this._tabKeydownHandler&&e.removeEventListener("keydown",this._tabKeydownHandler),this._tabKeydownHandler=t=>{if(!["ArrowLeft","ArrowRight","Home","End","Enter"," "].includes(t.key))return;const s=[...e.querySelectorAll(".session-tab")],i=s.indexOf(document.activeElement);if((t.key==="Enter"||t.key===" ")&&i>=0){t.preventDefault();const o=s[i].dataset.id;this.selectSession(o,{forceReload:!0});return}if(i<0)return;let n;switch(t.key){case"ArrowLeft":n=i>0?i-1:s.length-1;break;case"ArrowRight":n=i<s.length-1?i+1:0;break;case"Home":n=0;break;case"End":n=s.length-1;break;default:return}t.preventDefault(),s[n]?.focus()},e.addEventListener("keydown",this._tabKeydownHandler)}handleSessionTabClick(e,t){return e?.preventDefault?.(),!(typeof KeyboardHandler<"u"&&KeyboardHandler.keyboardVisible===!0)&&MobileDetection.isTouchDevice()&&document.activeElement?.blur?.(),this.selectSession(t,{forceReload:!0})}syncSessionOrder(){const e=new Set(this.sessions.keys()),s=this.loadSessionOrder().filter(o=>e.has(o)),i=new Set(s),n=[...e].filter(o=>!i.has(o));this.sessionOrder=[...s,...n]}loadSessionOrder(){try{const e=localStorage.getItem("codeman-session-order");return e?JSON.parse(e):[]}catch{return[]}}saveSessionOrder(){try{localStorage.setItem("codeman-session-order",JSON.stringify(this.sessionOrder))}catch{}}setupTabDragHandlers(){const e=this.$("sessionTabs");e.querySelectorAll(".session-tab[data-id]").forEach(s=>{s.setAttribute("draggable","true"),s.addEventListener("dragstart",i=>{this.draggedTabId=s.dataset.id,s.classList.add("dragging"),i.dataTransfer.effectAllowed="move",i.dataTransfer.setData("text/plain",s.dataset.id)}),s.addEventListener("dragend",()=>{s.classList.remove("dragging"),this.draggedTabId=null,e.querySelectorAll(".session-tab").forEach(i=>{i.classList.remove("drag-over-left","drag-over-right")})}),s.addEventListener("dragover",i=>{if(i.preventDefault(),!this.draggedTabId||this.draggedTabId===s.dataset.id)return;i.dataTransfer.dropEffect="move";const n=s.getBoundingClientRect(),o=n.left+n.width/2,a=i.clientX<o;s.classList.toggle("drag-over-left",a),s.classList.toggle("drag-over-right",!a)}),s.addEventListener("dragleave",()=>{s.classList.remove("drag-over-left","drag-over-right")}),s.addEventListener("drop",i=>{if(i.preventDefault(),s.classList.remove("drag-over-left","drag-over-right"),!this.draggedTabId||this.draggedTabId===s.dataset.id)return;const n=s.dataset.id,o=this.draggedTabId,a=s.getBoundingClientRect(),r=a.left+a.width/2,l=i.clientX<r,h=this.sessionOrder.indexOf(o);let d=this.sessionOrder.indexOf(n);h===-1||d===-1||(this.sessionOrder.splice(h,1),d=this.sessionOrder.indexOf(n),d!==-1&&(l?this.sessionOrder.splice(d,0,o):this.sessionOrder.splice(d+1,0,o),this.saveSessionOrder(),this._fullRenderSessionTabs()))})})}moveActiveTabLeft(){if(!this.activeSessionId)return;const e=this.sessionOrder.indexOf(this.activeSessionId);e<=0||([this.sessionOrder[e-1],this.sessionOrder[e]]=[this.sessionOrder[e],this.sessionOrder[e-1]],this.saveSessionOrder(),this._fullRenderSessionTabs())}moveActiveTabRight(){if(!this.activeSessionId)return;const e=this.sessionOrder.indexOf(this.activeSessionId);e===-1||e>=this.sessionOrder.length-1||([this.sessionOrder[e],this.sessionOrder[e+1]]=[this.sessionOrder[e+1],this.sessionOrder[e]],this.saveSessionOrder(),this._fullRenderSessionTabs())}getShortId(e){if(!e)return"";let t=this._shortIdCache.get(e);return t||(t=e.slice(0,8),this._shortIdCache.set(e,t)),t}getSessionName(e){return e.name?e.name:e.workingDir?e.workingDir.split("/").pop()||e.workingDir:this.getShortId(e.id)}_notifySession(e,t,s,i,n){const o=this.sessions.get(e);this.notificationManager?.notify({urgency:t,category:s,sessionId:e,sessionName:o?.name||this.getShortId(e),title:i,message:n})}_cleanupPreviousSession(e){this._disconnectWs();const t=document.getElementById("cjkInput");t&&(t.value=""),this._clearTimer("flickerFilterTimeout"),this.flickerFilterBuffer="",this.flickerFilterActive=!1,this._tabCompletionSessionId=null,this._tabCompletionRetries=0,this._tabCompletionBaseText=null,this._clearTimer("_tabCompletionFallback"),this._clearTimer("_clientDropRecoveryTimer"),this._clearTimer("syncWaitTimeout"),this.pendingWrites=[],this.writeFrameScheduled=!1,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._bufferLoadOwner=null,this._chunkedWriteGen=(this._chunkedWriteGen||0)+1;try{const s=this.terminal?._core?._compositionHelper;if(s?._isComposing){s._isComposing=!1;const i=this.terminal?.element?.querySelector(".xterm-helper-textarea");i&&i.dispatchEvent(new CompositionEvent("compositionend",{data:""}))}}catch{}if(this.activeSessionId){const s=this._localEchoOverlay?.pendingText||"",i=this._localEchoOverlay?.getFlushed()?.count||0,n=this._localEchoOverlay?.getFlushed()?.text||"";s&&this._sendInputAsync(this.activeSessionId,s);const o=i+s.length;o>0&&(this._flushedOffsets||(this._flushedOffsets=new Map),this._flushedTexts||(this._flushedTexts=new Map),this._flushedOffsets.set(this.activeSessionId,o),this._flushedTexts.set(this.activeSessionId,n+s))}this._localEchoOverlay?.clear(),this._localEchoOverlay&&!this._flushedOffsets?.has(e)&&this._localEchoOverlay.suppressBufferDetection()}_resetTerminalForReplay(){this.terminal.reset(),this.terminal.write("\x1B[3J\x1B[H\x1B[2J")}_shouldFocusTerminalForTabSwitch(){return typeof MobileDetection>"u"||!MobileDetection.isTouchDevice()?!0:typeof KeyboardHandler<"u"&&KeyboardHandler.keyboardVisible}async selectSession(e,t={}){if(!this.isSoloWindow&&this.detachedSessions.has(e)&&this._raiseDetached(e))return;const s=t?.forceReload===!0;if(this.activeSessionId===e&&!s)return;this.activeSessionId===e&&s&&(this.terminalBufferCache?.delete(e),this._clearTimer("syncWaitTimeout"),this.pendingWrites=[],this.writeFrameScheduled=!1,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._chunkedWriteGen=(this._chunkedWriteGen||0)+1,this.activeSessionId=null);const i=this._shouldFocusTerminalForTabSwitch();i&&this.terminal&&this.terminal.focus();const n=performance.now(),o=this.sessions.get(e)?.name||e.slice(0,8);_crashDiag.log(`SELECT: ${o}`),console.log(`[CRASH-DIAG] selectSession START: ${e.slice(0,8)}`);const a=++this._selectGeneration;if(this._setTerminalLoadState(e,a,"resizing"),a!==this._selectGeneration){this._clearTerminalLoadState(e,a);return}this._cleanupPreviousSession(e),this.activeSessionId=e;try{localStorage.setItem("codeman-active-session",e)}catch{}this._updateSseSubscription(e),this.hideWelcome(),this.clearPendingHooks(e,"idle_prompt"),this._updateActiveTabImmediate(e),this.renderSessionTabs(),this._updateLocalEchoState(),this._flushedOffsets?.has(e)&&this._localEchoOverlay&&this._localEchoOverlay.setFlushed(this._flushedOffsets.get(e),this._flushedTexts?.get(e)||"",!1);const r=document.querySelector(`.session-tab.active[data-id="${e}"]`);r&&(r.classList.add("tab-glow"),r.addEventListener("animationend",()=>r.classList.remove("tab-glow"),{once:!0}));const l=this.sessions.get(e);if(this.currentSessionWorkingDir=l?.workingDir||null,l&&l.pid===null)try{const d=l.mode==="shell"?`/api/sessions/${e}/shell`:`/api/sessions/${e}/interactive`;await fetch(d,{method:"POST"}),l.status="busy"}catch(d){console.error("Failed to attach to restored session:",d)}this._restoringFlushedState=!0;const h=this._beginBufferLoad(a);try{this.fitAddon&&this.fitAddon.fit();const d=await this.sendResize(e,{forceHttp:!0}).catch(()=>!1);if(this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}const p=l&&(l.status==="busy"||l.status==="working"),f=this.terminalBufferCache.get(e);if(f&&!p){if(_crashDiag.log(`CACHE_WRITE: ${(f.length/1024).toFixed(0)}KB`),this._setTerminalLoadState(e,a,"replaying"),this._resetTerminalForReplay(),await this.chunkedTerminalWrite(f,TERMINAL_CHUNK_SIZE,h),this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}this.terminal.scrollToBottom(),_crashDiag.log("CACHE_DONE")}else p&&(this._resetTerminalForReplay(),_crashDiag.log("CACHE_SKIP_BUSY"));if(l?.mode!=="shell"&&d&&(await new Promise(m=>setTimeout(m,TUI_REDRAW_SETTLE_MS)),this._isStaleSelect(a))){this._clearTerminalLoadState(e,a);return}this._setTerminalLoadState(e,a,"fetching"),_crashDiag.log("FETCH_START");const E=await fetch(`/api/sessions/${e}/terminal?tail=${TERMINAL_TAIL_SIZE}`);if(this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}const _=(await E.json())?.data??{};if(_crashDiag.log(`FETCH_DONE: ${_.terminalBuffer?(_.terminalBuffer.length/1024).toFixed(0)+"KB":"empty"} truncated=${_.truncated}`),_.terminalBuffer){if(p||_.terminalBuffer!==f){if(_crashDiag.log(`REWRITE: ${(_.terminalBuffer.length/1024).toFixed(0)}KB`),this._setTerminalLoadState(e,a,"replaying"),this._resetTerminalForReplay(),_.truncated&&this.terminal.write(`\x1B[90m... (earlier output truncated for performance) ...\x1B[0m\r
|
|
32
|
+
\r
|
|
33
|
+
`),await this.chunkedTerminalWrite(_.terminalBuffer,TERMINAL_CHUNK_SIZE,h),this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}this.terminal.scrollToBottom()}if(this.terminalBufferCache.set(e,_.terminalBuffer),this.terminalBufferCache.size>20){const b=this.terminalBufferCache.keys().next().value;this.terminalBufferCache.delete(b)}}else f||this._resetTerminalForReplay();if(this._isLoadingBuffer&&this._finishBufferLoad(h),this._restoringFlushedState=!1,this._flushedOffsets?.has(e)&&this._localEchoOverlay){this._localEchoOverlay.setFlushed(this._flushedOffsets.get(e),this._flushedTexts?.get(e)||"",!1);const m=this._localEchoOverlay;this.terminal.write("",()=>{m.hasPending&&m.rerender()})}this.sendResize(e),(typeof requestIdleCallback=="function"?requestIdleCallback:m=>setTimeout(m,16))(()=>{if(a!==this._selectGeneration)return;this.respawnStatus[e]?(this.showRespawnBanner(),this.updateRespawnBanner(this.respawnStatus[e].state),document.getElementById("respawnCycleCount").textContent=this.respawnStatus[e].cycleCount||0,this.updateCountdownTimerDisplay(),this.updateActionLogDisplay(),Object.keys(this.respawnCountdownTimers[e]||{}).length>0&&this.startCountdownInterval()):(this.hideRespawnBanner(),this.stopCountdownInterval());const m=document.getElementById("taskPanel");m&&m.classList.contains("open")&&this.renderTaskPanel();const b=this.sessions.get(e);if(b&&(b.ralphLoop||b.ralphTodos)&&this.updateRalphState(e,{loop:b.ralphLoop,todos:b.ralphTodos}),this.renderRalphStatePanel(),this.updateCliInfoDisplay(),this.renderProjectInsightsPanel(),this.updateSubagentWindowVisibility(),this.loadAppSettingsFromStorage().showFileBrowser){const S=this.$("fileBrowserPanel");if(S&&(S.classList.add("visible"),this.loadFileBrowser(e),!this.fileBrowserDragListeners)){const w=S.querySelector(".file-browser-header");if(w){const y=()=>{if(!S.style.left){const g=S.getBoundingClientRect();S.style.left=`${g.left}px`,S.style.top=`${g.top}px`,S.style.right="auto"}};w.addEventListener("mousedown",y),w.addEventListener("touchstart",y,{passive:!0}),this.fileBrowserDragListeners=this.makeWindowDraggable(S,w),this.fileBrowserDragListeners._onFirstDrag=y}}}}),this._connectWs(e),_crashDiag.log("FOCUS"),i&&this.terminal&&this.terminal.focus(),this.scrollToLastNonEmptyLine(),this._clearTerminalLoadState(e,a),_crashDiag.log(`SELECT_DONE: ${(performance.now()-n).toFixed(0)}ms`),console.log(`[CRASH-DIAG] selectSession DONE: ${e.slice(0,8)} in ${(performance.now()-n).toFixed(0)}ms`)}catch(d){this._isLoadingBuffer&&this._finishBufferLoad(h),this._restoringFlushedState=!1,this._setTerminalLoadState(e,a,"failed"),console.error("Failed to load session terminal:",d)}}_cleanupSessionData(e){this._activeRename?.sessionId===e&&this._activeRename.cancel(),this.sessions.delete(e);const t=this.sessionOrder.indexOf(e);t!==-1&&(this.sessionOrder.splice(t,1),this.saveSessionOrder()),this.terminalBuffers.delete(e),this.terminalBufferCache.delete(e),this._flushedOffsets?.delete(e),this._flushedTexts?.delete(e),this._inputQueue.delete(e),this.ralphStates.delete(e),this.ralphClosedSessions.delete(e),this.projectInsights.delete(e),this.pendingHooks.delete(e),this.tabAlerts.delete(e),this.terminalLoadStates.delete(e),this.clearCountdownTimers(e),this.closeSessionLogViewerWindows(e),this.closeSessionImagePopups(e),this.closeSessionSubagentWindows(e,!0);const s=this.idleTimers.get(e);s&&(clearTimeout(s),this.idleTimers.delete(e)),delete this.respawnStatus[e],delete this.respawnTimers[e],delete this.respawnCountdownTimers[e],delete this.respawnActionLogs[e]}async closeSession(e,t=!0){try{if(await this._apiDelete(`/api/sessions/${e}?killMux=${t}`),this._cleanupSessionData(e),this.activeSessionId===e){this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}if(this.sessionOrder.length>0&&this.sessions.size>0){const s=this.sessionOrder[0];this.selectSession(s)}else this.terminal.clear(),this.showWelcome(),this.renderRalphStatePanel()}this.renderSessionTabs(),t?this.showToast("Session closed and tmux killed","success"):this.showToast("Tab hidden, tmux still running","info")}catch{this.showToast("Failed to close session","error")}}requestCloseSession(e){const t=this.sessions.get(e);if(!t)return;this.pendingCloseSessionId=e;const s=this.getSessionName(t),i=document.getElementById("closeConfirmSessionName");i.textContent=s;const n=document.getElementById("closeConfirmKillTitle");n&&(n.textContent=t.mode==="opencode"?"Kill Tmux & OpenCode":t.mode==="codex"?"Kill Tmux & Codex":"Kill Tmux & Claude Code"),document.getElementById("closeConfirmModal").classList.add("active")}cancelCloseSession(){this.pendingCloseSessionId=null,document.getElementById("closeConfirmModal").classList.remove("active")}async confirmCloseSession(e=!0){const t=this.pendingCloseSessionId;this.cancelCloseSession(),t&&await this.closeSession(t,e)}nextSession(){if(this.sessionOrder.length<=1)return;const t=(this.sessionOrder.indexOf(this.activeSessionId)+1)%this.sessionOrder.length;this.selectSession(this.sessionOrder[t])}prevSession(){if(this.sessionOrder.length<=1)return;const t=(this.sessionOrder.indexOf(this.activeSessionId)-1+this.sessionOrder.length)%this.sessionOrder.length;this.selectSession(this.sessionOrder[t])}goHome(){this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.terminal.clear(),this.showWelcome(),this.renderSessionTabs(),this.renderRalphStatePanel()}ralphWizardStep=1;ralphWizardConfig={taskDescription:"",completionPhrase:"COMPLETE",maxIterations:10,caseName:"testcase",enableRespawn:!1,generatedPlan:null,planGenerated:!1,skipPlanGeneration:!1,planDetailLevel:"detailed",existingPlan:null,useExistingPlan:!1};planLoadingTimer=null;planLoadingStartTime=null;async killActiveSession(){if(!this.activeSessionId){this.showToast("No active session","warning");return}await this.closeSession(this.activeSessionId)}async killAllSessions(){if(this.sessions.size!==0&&confirm(`Kill all ${this.sessions.size} session(s)?`))try{await this._apiDelete("/api/sessions"),this.sessions.clear(),this.terminalBuffers.clear(),this.terminalBufferCache.clear(),this.terminalLoadStates.clear(),this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.respawnStatus={},this.respawnCountdownTimers={},this.respawnActionLogs={},this.stopCountdownInterval(),this.hideRespawnBanner(),this.renderSessionTabs(),this.terminal.clear(),this.showWelcome(),this.showToast("All sessions killed","success")}catch{this.showToast("Failed to kill sessions","error")}}showTimer(){document.getElementById("timerBanner").style.display="flex",this.updateTimer(),this.timerInterval=setInterval(()=>this.updateTimer(),1e3)}hideTimer(){document.getElementById("timerBanner").style.display="none",this.timerInterval&&(clearInterval(this.timerInterval),this.timerInterval=null)}updateTimer(){if(!this.currentRun||this.currentRun.status!=="running")return;const e=Date.now(),t=Math.max(0,this.currentRun.endAt-e),s=this.currentRun.endAt-this.currentRun.startedAt,i=e-this.currentRun.startedAt,n=Math.min(100,i/s*100);document.getElementById("timerValue").textContent=this.formatTime(t),document.getElementById("timerProgress").style.width=`${n}%`,document.getElementById("timerMeta").textContent=`${this.currentRun.completedTasks} tasks | $${this.currentRun.totalCost.toFixed(2)}`}async stopCurrentRun(){if(this.currentRun)try{await fetch(`/api/scheduled/${this.currentRun.id}`,{method:"DELETE"})}catch{this.showToast("Failed to stop run","error")}}formatTime(e){const t=Math.floor(e/1e3),s=Math.floor(t/3600),i=Math.floor(t%3600/60),n=t%60;return`${s.toString().padStart(2,"0")}:${i.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}`}updateCost(){this.updateTokens()}updateTokens(){this._clearTimer("_updateTokensTimeout"),this._updateTokensTimeout=setTimeout(()=>{this._updateTokensTimeout=null,this._updateTokensImmediate()},200)}_updateTokensImmediate(){let e=0,t=0;this.globalStats?(e=this.globalStats.totalInputTokens||0,t=this.globalStats.totalOutputTokens||0):this.sessions.forEach(a=>{a.tokens&&(e+=a.tokens.input||0,t+=a.tokens.output||0)});const s=e+t;this.totalTokens=s;const i=this.formatTokens(s),n=this.estimateCost(e,t),o=this.$("headerTokens");if(o){const r=this.loadAppSettingsFromStorage().showCost??!1;o.textContent=s>0?r?`${i} tokens \xB7 $${n.toFixed(2)}`:`${i} tokens`:"0 tokens",o.title=this.globalStats?`Lifetime: ${this.globalStats.totalSessionsCreated} sessions created${r?`
|
|
34
|
+
Estimated cost based on Claude Opus pricing`:""}`:`Token usage across active sessions${r?`
|
|
35
|
+
Estimated cost based on Claude Opus pricing`:""}`}}}try{for(let c=0;c<localStorage.length;c++){const e=localStorage.key(c);if(e&&(e.startsWith("claudeman-")||e.startsWith("claudeman_"))){const t=e.replace(/^claudeman[-_]/,s=>"codeman"+s.charAt(s.length-1));localStorage.getItem(t)===null&&localStorage.setItem(t,localStorage.getItem(e))}}}catch{}let app;document.addEventListener("DOMContentLoaded",()=>{app=new CodemanApp,window.app=app}),window.MobileDetection=MobileDetection;
|
|
Binary file
|
|
Binary file
|
|
@@ -51,6 +51,7 @@ const GROUPING_TIMEOUT_MS = 5000; // 5 seconds - notification grouping
|
|
|
51
51
|
const NOTIFICATION_LIST_CAP = 100; // Max notifications in list
|
|
52
52
|
const TITLE_FLASH_INTERVAL_MS = 1500; // Title flash rate
|
|
53
53
|
const BROWSER_NOTIF_RATE_LIMIT_MS = 3000; // Rate limit for browser notifications
|
|
54
|
+
const MOBILE_RESIZE_RETRY_MS = 30000; // Small-viewport resize re-send while a desktop sizing claim is hot
|
|
54
55
|
const AUTO_CLOSE_NOTIFICATION_MS = 8000; // Auto-close browser notifications
|
|
55
56
|
const THROTTLE_DELAY_MS = 100; // General UI throttle delay
|
|
56
57
|
const TERMINAL_CHUNK_SIZE = 32 * 1024; // 32KB chunks for terminal buffer loading
|
|
@@ -243,6 +244,9 @@ const SSE_EVENTS = {
|
|
|
243
244
|
SESSION_WORKING: 'session:working',
|
|
244
245
|
SESSION_AUTO_CLEAR: 'session:autoClear',
|
|
245
246
|
SESSION_AUTO_COMPACT: 'session:autoCompact',
|
|
247
|
+
SESSION_LIMIT_PAUSE_SCHEDULED: 'session:limitPauseScheduled',
|
|
248
|
+
SESSION_LIMIT_RESUME: 'session:limitResume',
|
|
249
|
+
SESSION_LIMIT_RESUME_CANCELLED: 'session:limitResumeCancelled',
|
|
246
250
|
SESSION_CLI_INFO: 'session:cliInfo',
|
|
247
251
|
SESSION_MESSAGE: 'session:message',
|
|
248
252
|
SESSION_INTERACTIVE: 'session:interactive',
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -16,16 +16,16 @@
|
|
|
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.
|
|
20
|
-
<link rel="stylesheet" href="mobile.
|
|
19
|
+
<link rel="stylesheet" href="styles.c14ecd8a.css">
|
|
20
|
+
<link rel="stylesheet" href="mobile.c7513aed.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'">
|
|
23
23
|
<noscript><link rel="stylesheet" href="vendor/xterm.css"></noscript>
|
|
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.8fa1a65f.js" as="script">
|
|
28
|
+
<link rel="preload" href="app.a23f8bf6.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>
|
|
@@ -69,10 +69,6 @@
|
|
|
69
69
|
<span class="logo" onclick="app.goHome()" title="Go to main page">Codeman</span>
|
|
70
70
|
</div>
|
|
71
71
|
|
|
72
|
-
<button class="mobile-header-utility-toggle" id="mobileHeaderUtilityToggle" type="button" aria-label="Toggle header utilities" aria-controls="headerRight" aria-expanded="false" title="Header utilities">
|
|
73
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg>
|
|
74
|
-
</button>
|
|
75
|
-
|
|
76
72
|
<!-- Session Tabs -->
|
|
77
73
|
<div class="session-tabs" id="sessionTabs" role="tablist" aria-label="Session tabs">
|
|
78
74
|
</div>
|
|
@@ -608,17 +604,19 @@
|
|
|
608
604
|
<div class="modal-tab-content" id="respawn-tab">
|
|
609
605
|
<!-- Respawn Settings Section -->
|
|
610
606
|
<div class="session-respawn-section" id="sessionRespawnSection">
|
|
611
|
-
<div class="
|
|
612
|
-
<
|
|
613
|
-
<
|
|
614
|
-
<span
|
|
615
|
-
</
|
|
616
|
-
<
|
|
617
|
-
|
|
618
|
-
<button class="btn-toolbar btn-danger btn-sm" onclick="app.stopRespawnFromModal()" id="modalStopRespawnBtn" style="display: none;">Stop</button>
|
|
619
|
-
</div>
|
|
607
|
+
<div class="auto-resume-box">
|
|
608
|
+
<label class="checkbox-inline">
|
|
609
|
+
<input type="checkbox" id="modalAutoResumeEnabled" onchange="app.autoSaveAutoResume()">
|
|
610
|
+
<span>Auto-resume when usage limit resets</span>
|
|
611
|
+
</label>
|
|
612
|
+
<span class="auto-resume-status" id="autoResumeStatus"></span>
|
|
613
|
+
<span class="form-hint">If Claude pauses on a usage limit ("limit reached · resets 3pm"), Codeman waits for the reset time and automatically continues the work. Independent of the respawn loop below.</span>
|
|
620
614
|
</div>
|
|
621
615
|
|
|
616
|
+
<div class="respawn-loop-box">
|
|
617
|
+
<div class="respawn-loop-title">Respawn loop</div>
|
|
618
|
+
<span class="form-hint respawn-loop-hint">One autonomous work cycle: whenever Claude goes idle, Codeman sends the update prompt, optionally runs /clear + /init, and kickstarts the next round — repeating for the chosen duration. All settings below belong to this loop; configure them, then press Enable.</span>
|
|
619
|
+
|
|
622
620
|
<div class="form-row">
|
|
623
621
|
<label>Duration</label>
|
|
624
622
|
<div class="duration-presets">
|
|
@@ -655,10 +653,10 @@
|
|
|
655
653
|
<p class="form-hint" id="presetDescriptionHint"></p>
|
|
656
654
|
</div>
|
|
657
655
|
|
|
658
|
-
<div class="form-section-header">
|
|
656
|
+
<div class="form-section-header">Cycle Steps</div>
|
|
659
657
|
<div class="form-row">
|
|
660
658
|
<label>1. Update Prompt</label>
|
|
661
|
-
<textarea id="modalRespawnPrompt" rows="
|
|
659
|
+
<textarea id="modalRespawnPrompt" rows="1" placeholder="Prompt to send when idle" onchange="app.autoSaveRespawnConfig()" style="resize: vertical; min-height: 30px;">update all the docs and CLAUDE.md</textarea>
|
|
662
660
|
</div>
|
|
663
661
|
|
|
664
662
|
<div class="respawn-options-row" style="margin: 8px 0;">
|
|
@@ -670,22 +668,29 @@
|
|
|
670
668
|
<input type="checkbox" id="modalRespawnSendInit" checked onchange="app.autoSaveRespawnConfig()">
|
|
671
669
|
<span>3. Send /init</span>
|
|
672
670
|
</label>
|
|
671
|
+
<label class="checkbox-inline" title="Presses Enter for plan approvals and default question options">
|
|
672
|
+
<input type="checkbox" id="modalRespawnAutoAccept" checked onchange="app.autoSaveRespawnConfig()">
|
|
673
|
+
<span>Auto-accept prompts</span>
|
|
674
|
+
</label>
|
|
673
675
|
</div>
|
|
674
676
|
|
|
675
677
|
<div class="form-row">
|
|
676
678
|
<label>4. Kickstart Prompt</label>
|
|
677
|
-
<textarea id="modalRespawnKickstart" rows="
|
|
678
|
-
<span class="form-hint">Sent only when /init completes but Claude stays idle</span>
|
|
679
|
+
<textarea id="modalRespawnKickstart" rows="1" placeholder="Optional: prompt if /init doesn't trigger work" onchange="app.autoSaveRespawnConfig()" style="resize: vertical; min-height: 30px;"></textarea>
|
|
680
|
+
<span class="form-hint">Sent only when /init completes but Claude stays idle · Auto-accept presses Enter for plan approvals and default options</span>
|
|
679
681
|
</div>
|
|
680
682
|
|
|
681
|
-
<div class="
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
<
|
|
685
|
-
|
|
686
|
-
|
|
683
|
+
<div class="respawn-header">
|
|
684
|
+
<div class="session-respawn-status" id="sessionRespawnStatus">
|
|
685
|
+
<span class="respawn-status-indicator"></span>
|
|
686
|
+
<span class="respawn-status-text">Not active</span>
|
|
687
|
+
</div>
|
|
688
|
+
<div class="respawn-actions">
|
|
689
|
+
<button class="btn-toolbar btn-success btn-sm" onclick="app.enableRespawnFromModal()" id="modalEnableRespawnBtn">Enable</button>
|
|
690
|
+
<button class="btn-toolbar btn-danger btn-sm" onclick="app.stopRespawnFromModal()" id="modalStopRespawnBtn" style="display: none;">Stop</button>
|
|
691
|
+
</div>
|
|
687
692
|
</div>
|
|
688
|
-
|
|
693
|
+
</div><!-- End respawn-loop-box -->
|
|
689
694
|
</div>
|
|
690
695
|
</div><!-- End respawn-tab -->
|
|
691
696
|
|
|
@@ -1879,20 +1884,20 @@
|
|
|
1879
1884
|
<!-- Lines drawn dynamically -->
|
|
1880
1885
|
</svg>
|
|
1881
1886
|
|
|
1882
|
-
<script defer src="constants.
|
|
1883
|
-
<script defer src="mobile-handlers.
|
|
1887
|
+
<script defer src="constants.8fa1a65f.js"></script>
|
|
1888
|
+
<script defer src="mobile-handlers.763a7439.js"></script>
|
|
1884
1889
|
<script defer src="voice-input.085e9e73.js"></script>
|
|
1885
1890
|
<script defer src="notification-manager.9c984ac2.js"></script>
|
|
1886
1891
|
<script defer src="keyboard-accessory.bc753cc7.js"></script>
|
|
1887
1892
|
<script defer src="input-cjk.b8686b5e.js"></script>
|
|
1888
|
-
<script defer src="app.
|
|
1889
|
-
<script defer src="terminal-ui.
|
|
1893
|
+
<script defer src="app.a23f8bf6.js"></script>
|
|
1894
|
+
<script defer src="terminal-ui.1f5b45cf.js"></script>
|
|
1890
1895
|
<script defer src="respawn-ui.2d249da9.js"></script>
|
|
1891
1896
|
<script defer src="ralph-panel.6de2d0f8.js"></script>
|
|
1892
1897
|
<script defer src="orchestrator-panel.js"></script>
|
|
1893
|
-
<script defer src="settings-ui.
|
|
1898
|
+
<script defer src="settings-ui.3a341938.js"></script>
|
|
1894
1899
|
<script defer src="panels-ui.6bb3169f.js"></script>
|
|
1895
|
-
<script defer src="session-ui.
|
|
1900
|
+
<script defer src="session-ui.34f25fdf.js"></script>
|
|
1896
1901
|
<script defer src="ralph-wizard.13a1831e.js"></script>
|
|
1897
1902
|
<script defer src="api-client.c9b1cddc.js"></script>
|
|
1898
1903
|
<script defer src="subagent-windows.a366a4ad.js"></script>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|