claude-simple 0.2.24 → 0.2.26
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 +25 -2
- package/client.js +20 -3
- package/dist/app.0.2.26.min.js +1179 -0
- package/dist/index.html +2 -2
- package/dist/style.0.2.26.min.css +1851 -0
- package/package.json +1 -1
- package/public/api.js +10 -3
- package/public/app.js +1 -1
- package/public/chat.js +260 -12
- package/public/files.js +50 -0
- package/public/index.html +2 -2
- package/public/locks.js +139 -0
- package/public/privacy.html +111 -0
- package/public/router.js +5 -0
- package/public/settings.js +312 -63
- package/public/shell/boot.js +3 -0
- package/public/shell/notes.js +11 -3
- package/public/shell/session-list.js +50 -2
- package/public/shell/session-nav.js +49 -12
- package/public/shell/templates.js +5 -1
- package/public/shell.js +16 -4
- package/public/state.js +6 -2
- package/public/style.css +227 -3
- package/public/sw.js +3 -3
- package/public/ui.min.css +1 -1
- package/src/agent-env.js +211 -0
- package/src/agents/claude.js +10 -3
- package/src/agents/grok.js +24 -0
- package/src/agents/meta-agent.js +89 -17
- package/src/agents/models.js +30 -1
- package/src/agents/sessions.js +12 -2
- package/src/app.js +512 -25
- package/src/collab.js +267 -0
- package/src/runtime/external-session-sync.js +106 -0
- package/src/runtime/protocol.js +11 -2
- package/dist/app.0.2.24.min.js +0 -1104
- package/dist/style.0.2.24.min.css +0 -1627
package/public/locks.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// locks.js — lease locks for the current session (hub or standalone)
|
|
2
|
+
import { ctx, currentAgent, selectedMachineId } from './state.js';
|
|
3
|
+
import { api } from './api.js';
|
|
4
|
+
|
|
5
|
+
const TTL_SEC = 60;
|
|
6
|
+
const HEARTBEAT_MS = 20_000; // design: ≤25s
|
|
7
|
+
|
|
8
|
+
let heartbeatTimer = null;
|
|
9
|
+
let acquireInFlight = null;
|
|
10
|
+
|
|
11
|
+
export function sessionLockKey({
|
|
12
|
+
sessionId = ctx.sessionId,
|
|
13
|
+
agent = ctx.agent || currentAgent.peek() || 'claude',
|
|
14
|
+
machineId = ctx.machineId || selectedMachineId.peek() || 'local',
|
|
15
|
+
} = {}) {
|
|
16
|
+
if (!sessionId) return null;
|
|
17
|
+
return `session:${machineId || 'local'}:${agent || 'claude'}:${sessionId}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function canWriteNow() {
|
|
21
|
+
const role = ctx.role || 'editor';
|
|
22
|
+
if (role === 'viewer') return false;
|
|
23
|
+
if (ctx.lock?.foreign) return false;
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function emitLockChange() {
|
|
28
|
+
document.dispatchEvent(new CustomEvent('session-lock-changed', { detail: ctx.lock }));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function setLockState(next) {
|
|
32
|
+
ctx.lock = { key: null, token: null, holder: null, held: false, foreign: false, ...(next || {}) };
|
|
33
|
+
emitLockChange();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function acquireSessionLock(identity = {}) {
|
|
37
|
+
const key = sessionLockKey(identity);
|
|
38
|
+
if (!key) {
|
|
39
|
+
setLockState({ foreign: false, held: true, unmanaged: true });
|
|
40
|
+
return { ok: true, skipped: true };
|
|
41
|
+
}
|
|
42
|
+
const role = ctx.role || 'editor';
|
|
43
|
+
if (role === 'viewer') {
|
|
44
|
+
setLockState({ key, held: false, foreign: true, holder: 'viewer' });
|
|
45
|
+
return { ok: false, error: 'viewers cannot take exclusive locks', code: 403 };
|
|
46
|
+
}
|
|
47
|
+
if (ctx.lock?.key && ctx.lock.key !== key) {
|
|
48
|
+
await releaseSessionLock();
|
|
49
|
+
}
|
|
50
|
+
if (acquireInFlight) return acquireInFlight;
|
|
51
|
+
acquireInFlight = (async () => {
|
|
52
|
+
try {
|
|
53
|
+
const body = {
|
|
54
|
+
key,
|
|
55
|
+
kind: 'session',
|
|
56
|
+
ttlSec: TTL_SEC,
|
|
57
|
+
exclusive: true,
|
|
58
|
+
...(ctx.lock?.token && ctx.lock?.key === key ? { token: ctx.lock.token } : {}),
|
|
59
|
+
};
|
|
60
|
+
const lock = await api('POST', '/api/collab/locks', body, undefined, { hub: true });
|
|
61
|
+
setLockState({
|
|
62
|
+
key,
|
|
63
|
+
token: lock.token,
|
|
64
|
+
holder: lock.holder,
|
|
65
|
+
held: true,
|
|
66
|
+
foreign: false,
|
|
67
|
+
expiresAt: lock.expiresAt,
|
|
68
|
+
version: lock.version,
|
|
69
|
+
});
|
|
70
|
+
startLockHeartbeat();
|
|
71
|
+
return { ok: true, lock };
|
|
72
|
+
} catch (err) {
|
|
73
|
+
const msg = String(err.message || err);
|
|
74
|
+
const locked = /locked|HTTP 423|423/.test(msg);
|
|
75
|
+
if (locked) {
|
|
76
|
+
setLockState({ key, held: false, foreign: true, holder: 'another user' });
|
|
77
|
+
return { ok: false, code: 423, error: msg };
|
|
78
|
+
}
|
|
79
|
+
// Collab API missing (older standalone): do not block typing.
|
|
80
|
+
setLockState({ key, held: true, foreign: false, unmanaged: true });
|
|
81
|
+
return { ok: true, skipped: true, error: msg };
|
|
82
|
+
} finally {
|
|
83
|
+
acquireInFlight = null;
|
|
84
|
+
}
|
|
85
|
+
})();
|
|
86
|
+
return acquireInFlight;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function heartbeatLock() {
|
|
90
|
+
const { key, token, held } = ctx.lock || {};
|
|
91
|
+
if (!key || !token || !held) return;
|
|
92
|
+
try {
|
|
93
|
+
const lock = await api('POST', '/api/collab/lock-action', {
|
|
94
|
+
key, action: 'heartbeat', token, ttlSec: TTL_SEC,
|
|
95
|
+
}, undefined, { hub: true });
|
|
96
|
+
if (lock?.token) ctx.lock.token = lock.token;
|
|
97
|
+
if (lock?.expiresAt) ctx.lock.expiresAt = lock.expiresAt;
|
|
98
|
+
} catch (err) {
|
|
99
|
+
const msg = String(err.message || err);
|
|
100
|
+
if (/token mismatch|HTTP 409|409|no lock|HTTP 404|404/.test(msg)) {
|
|
101
|
+
// Lost the lease — try to reclaim if we are still the holder.
|
|
102
|
+
const result = await acquireSessionLock();
|
|
103
|
+
if (!result.ok) stopLockHeartbeat();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function startLockHeartbeat() {
|
|
109
|
+
stopLockHeartbeat();
|
|
110
|
+
heartbeatTimer = setInterval(() => { heartbeatLock().catch(() => {}); }, HEARTBEAT_MS);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function stopLockHeartbeat() {
|
|
114
|
+
if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function releaseSessionLock() {
|
|
118
|
+
stopLockHeartbeat();
|
|
119
|
+
const { key, token } = ctx.lock || {};
|
|
120
|
+
setLockState({ held: false, foreign: false });
|
|
121
|
+
if (!key) return;
|
|
122
|
+
try {
|
|
123
|
+
await api('POST', '/api/collab/lock-action', {
|
|
124
|
+
key, action: 'release', token, ttlSec: TTL_SEC,
|
|
125
|
+
}, undefined, { hub: true });
|
|
126
|
+
} catch { /* TTL will expire */ }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function loadCollabMe() {
|
|
130
|
+
try {
|
|
131
|
+
const me = await api('GET', '/api/collab/me', undefined, undefined, { hub: true });
|
|
132
|
+
ctx.role = me?.role || ctx.role || 'editor';
|
|
133
|
+
ctx.username = me?.username || ctx.username || null;
|
|
134
|
+
return me;
|
|
135
|
+
} catch {
|
|
136
|
+
if (!ctx.role) ctx.role = 'editor';
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<meta name="theme-color" content="#0f172a">
|
|
7
|
+
<meta name="robots" content="index,follow">
|
|
8
|
+
<title>AgentUI Privacy Policy</title>
|
|
9
|
+
<style>
|
|
10
|
+
:root {
|
|
11
|
+
color-scheme: dark;
|
|
12
|
+
}
|
|
13
|
+
body {
|
|
14
|
+
margin: 0;
|
|
15
|
+
min-height: 100dvh;
|
|
16
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
17
|
+
background: #0f172a;
|
|
18
|
+
color: #e2e8f0;
|
|
19
|
+
line-height: 1.6;
|
|
20
|
+
}
|
|
21
|
+
main {
|
|
22
|
+
max-width: 42rem;
|
|
23
|
+
margin: 0 auto;
|
|
24
|
+
padding: 2.5rem 1.5rem 4rem;
|
|
25
|
+
}
|
|
26
|
+
h1 {
|
|
27
|
+
font-size: 1.6rem;
|
|
28
|
+
font-weight: 700;
|
|
29
|
+
margin: 0 0 0.35rem;
|
|
30
|
+
letter-spacing: -0.02em;
|
|
31
|
+
}
|
|
32
|
+
.updated {
|
|
33
|
+
color: #94a3b8;
|
|
34
|
+
font-size: 0.9rem;
|
|
35
|
+
margin: 0 0 1.75rem;
|
|
36
|
+
}
|
|
37
|
+
h2 {
|
|
38
|
+
font-size: 1.05rem;
|
|
39
|
+
margin: 1.75rem 0 0.6rem;
|
|
40
|
+
color: #cbd5e1;
|
|
41
|
+
}
|
|
42
|
+
p, li {
|
|
43
|
+
font-size: 0.95rem;
|
|
44
|
+
color: #cbd5e1;
|
|
45
|
+
}
|
|
46
|
+
p { margin: 0 0 0.85rem; }
|
|
47
|
+
ul {
|
|
48
|
+
margin: 0 0 0.85rem;
|
|
49
|
+
padding-left: 1.2rem;
|
|
50
|
+
}
|
|
51
|
+
li { margin: 0.25rem 0; }
|
|
52
|
+
a {
|
|
53
|
+
color: #818cf8;
|
|
54
|
+
text-decoration: none;
|
|
55
|
+
}
|
|
56
|
+
a:hover { text-decoration: underline; }
|
|
57
|
+
code {
|
|
58
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
59
|
+
font-size: 0.86em;
|
|
60
|
+
background: #1e293b;
|
|
61
|
+
border: 1px solid #334155;
|
|
62
|
+
border-radius: 0.3rem;
|
|
63
|
+
padding: 0.05rem 0.35rem;
|
|
64
|
+
}
|
|
65
|
+
</style>
|
|
66
|
+
</head>
|
|
67
|
+
<body>
|
|
68
|
+
<main>
|
|
69
|
+
<h1>AgentUI Privacy Policy</h1>
|
|
70
|
+
<p class="updated">Last updated: 31 August 2026</p>
|
|
71
|
+
|
|
72
|
+
<p>
|
|
73
|
+
AgentUI (bundle <code>online.llmlife.agentui</code>) is a mobile client for a self-hosted AI coding hub.
|
|
74
|
+
The iOS app is published by llmlife.
|
|
75
|
+
</p>
|
|
76
|
+
|
|
77
|
+
<h2>What the app does</h2>
|
|
78
|
+
<ul>
|
|
79
|
+
<li>You type a hub URL and credentials. The app talks only to the hub you configure (default <code>https://agentui.antisense.fun</code>).</li>
|
|
80
|
+
<li>Session chat, files, git, terminal, and memory stay on that hub / your machines. We (the App Store publisher) do not operate a public user database for AgentUI.</li>
|
|
81
|
+
</ul>
|
|
82
|
+
|
|
83
|
+
<h2>Data the iOS app may access on device</h2>
|
|
84
|
+
<ul>
|
|
85
|
+
<li><strong>Camera and photo library:</strong> only if you attach a photo to a session. Images are uploaded to YOUR hub.</li>
|
|
86
|
+
<li><strong>Files you pick:</strong> uploaded to YOUR hub.</li>
|
|
87
|
+
<li><strong>Local notifications:</strong> session-complete banners on this device. No Apple Push token is required for that.</li>
|
|
88
|
+
<li><strong>Account token / hub URL:</strong> stored in app preferences on device.</li>
|
|
89
|
+
</ul>
|
|
90
|
+
|
|
91
|
+
<h2>What we do not do</h2>
|
|
92
|
+
<ul>
|
|
93
|
+
<li>No third-party analytics SDK, no ads, no tracking pixels in the iOS app.</li>
|
|
94
|
+
<li>No sale of personal data.</li>
|
|
95
|
+
<li>We do not require you to create an Apple-ID-linked social account.</li>
|
|
96
|
+
</ul>
|
|
97
|
+
|
|
98
|
+
<h2>Hub operators</h2>
|
|
99
|
+
<p>
|
|
100
|
+
Whoever runs the hub you sign into is the controller for chat logs, files, and account data on that server.
|
|
101
|
+
If you use <code>https://agentui.antisense.fun</code>, contact the operator via the GitHub repo
|
|
102
|
+
<a href="https://github.com/forechoandlook/claude-simple-ui">https://github.com/forechoandlook/claude-simple-ui</a>.
|
|
103
|
+
</p>
|
|
104
|
+
|
|
105
|
+
<h2>Contact</h2>
|
|
106
|
+
<p>
|
|
107
|
+
<a href="https://github.com/forechoandlook/claude-simple-ui/issues">https://github.com/forechoandlook/claude-simple-ui/issues</a>
|
|
108
|
+
</p>
|
|
109
|
+
</main>
|
|
110
|
+
</body>
|
|
111
|
+
</html>
|
package/public/router.js
CHANGED
|
@@ -54,6 +54,11 @@ export function applyInitialRoute() {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
function applyHash(path) {
|
|
57
|
+
const pathname = String(path || '').split('?')[0];
|
|
58
|
+
if (pathname === '/settings') {
|
|
59
|
+
document.dispatchEvent(new CustomEvent('router:settings'));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
57
62
|
// /session/:id or /session/:id/:tab
|
|
58
63
|
const parsed = parseHashPath(path);
|
|
59
64
|
if (!parsed) {
|