claude-simple 0.2.25 → 0.2.27
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 +6 -3
- package/dist/app.0.2.27.min.js +1182 -0
- package/dist/index.html +2 -2
- package/dist/style.0.2.27.min.css +1851 -0
- package/package.json +1 -1
- package/public/api.js +9 -3
- package/public/app.js +1 -1
- package/public/chat.js +66 -23
- 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/shell/boot.js +3 -0
- package/public/shell/notes.js +6 -1
- package/public/shell/session-nav.js +4 -0
- package/public/shell/templates.js +7 -1
- package/public/shell.js +18 -5
- package/public/state.js +3 -0
- package/public/style.css +101 -3
- package/public/sw.js +3 -3
- package/public/ui.min.css +1 -1
- package/src/agents/meta-agent.js +89 -17
- package/src/agents/sessions.js +125 -0
- package/src/app.js +69 -18
- package/src/collab.js +84 -65
- package/src/runtime/protocol.js +8 -0
- package/dist/app.0.2.25.min.js +0 -1164
- package/dist/style.0.2.25.min.css +0 -1753
package/src/collab.js
CHANGED
|
@@ -15,6 +15,12 @@ export function roleAtLeast(have, need) {
|
|
|
15
15
|
export function createCollab({ filePath }) {
|
|
16
16
|
let data = { users: [], locks: {}, routes: {} };
|
|
17
17
|
let loaded = false;
|
|
18
|
+
let chain = Promise.resolve();
|
|
19
|
+
function withMutex(fn) {
|
|
20
|
+
const run = chain.then(fn, fn);
|
|
21
|
+
chain = run.then(() => undefined, () => undefined);
|
|
22
|
+
return run;
|
|
23
|
+
}
|
|
18
24
|
|
|
19
25
|
async function load() {
|
|
20
26
|
if (loaded) return data;
|
|
@@ -49,82 +55,95 @@ export function createCollab({ filePath }) {
|
|
|
49
55
|
function token() { return crypto.randomBytes(16).toString('hex'); }
|
|
50
56
|
|
|
51
57
|
async function acquire({ key, kind, holder, holderRole, lockToken, ttlMs, exclusive = true }) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (!key) return { code: 400, error: 'key required' };
|
|
58
|
-
ttlMs = Math.min(Math.max(ttlMs || DEFAULT_TTL_MS, 5_000), 5 * 60_000);
|
|
59
|
-
const cur = data.locks[key];
|
|
60
|
-
if (!cur) {
|
|
61
|
-
const l = {
|
|
62
|
-
key, kind, holder, holderRole,
|
|
63
|
-
token: lockToken || token(),
|
|
64
|
-
exclusive, acquiredAt: now(), expiresAt: now() + ttlMs, version: 1,
|
|
65
|
-
};
|
|
66
|
-
data.locks[key] = l;
|
|
67
|
-
await save();
|
|
68
|
-
return { code: 200, lock: { ...l } };
|
|
69
|
-
}
|
|
70
|
-
if (cur.holder === holder && (!lockToken || lockToken === cur.token)) {
|
|
71
|
-
cur.expiresAt = now() + ttlMs;
|
|
72
|
-
if (!lockToken) {
|
|
73
|
-
cur.token = token();
|
|
74
|
-
cur.version += 1;
|
|
58
|
+
return withMutex(async () => {
|
|
59
|
+
await load();
|
|
60
|
+
purgeLocks();
|
|
61
|
+
if (!roleAtLeast(holderRole, 'editor')) {
|
|
62
|
+
return { code: 403, error: 'viewers cannot take exclusive locks' };
|
|
75
63
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
64
|
+
if (!key) return { code: 400, error: 'key required' };
|
|
65
|
+
ttlMs = Math.min(Math.max(ttlMs || DEFAULT_TTL_MS, 5_000), 5 * 60_000);
|
|
66
|
+
const cur = data.locks[key];
|
|
67
|
+
if (!cur) {
|
|
68
|
+
const l = {
|
|
69
|
+
key, kind, holder, holderRole,
|
|
70
|
+
token: lockToken || token(),
|
|
71
|
+
exclusive, acquiredAt: now(), expiresAt: now() + ttlMs, version: 1,
|
|
72
|
+
};
|
|
73
|
+
data.locks[key] = l;
|
|
74
|
+
await save();
|
|
75
|
+
return { code: 200, lock: { ...l } };
|
|
76
|
+
}
|
|
77
|
+
// Same principal always reclaims. Never rotate the token on a token-less
|
|
78
|
+
// refresh: rotating locked the holder's other tabs out of their own session.
|
|
79
|
+
if (cur.holder === holder) {
|
|
80
|
+
cur.expiresAt = now() + ttlMs;
|
|
81
|
+
cur.holderRole = holderRole || cur.holderRole;
|
|
82
|
+
await save();
|
|
83
|
+
return { code: 200, lock: { ...cur } };
|
|
84
|
+
}
|
|
85
|
+
const pub = { ...cur, token: undefined };
|
|
86
|
+
return { code: 423, error: 'locked', lock: pub };
|
|
87
|
+
});
|
|
81
88
|
}
|
|
82
89
|
|
|
83
90
|
async function heartbeat({ key, holder, lockToken, ttlMs }) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
return withMutex(async () => {
|
|
92
|
+
await load();
|
|
93
|
+
purgeLocks();
|
|
94
|
+
const cur = data.locks[key];
|
|
95
|
+
if (!cur) return { code: 404, error: 'no lock' };
|
|
96
|
+
if (cur.holder !== holder || cur.token !== lockToken) {
|
|
97
|
+
return { code: 409, error: 'token mismatch' };
|
|
98
|
+
}
|
|
99
|
+
cur.expiresAt = now() + (ttlMs || DEFAULT_TTL_MS);
|
|
100
|
+
await save();
|
|
101
|
+
return { code: 200, lock: { ...cur } };
|
|
102
|
+
});
|
|
94
103
|
}
|
|
95
104
|
|
|
96
105
|
async function release({ key, holder, lockToken, force }) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
return withMutex(async () => {
|
|
107
|
+
await load();
|
|
108
|
+
const cur = data.locks[key];
|
|
109
|
+
if (!cur) return { code: 200 };
|
|
110
|
+
// Holder can drop their own lease without presenting the token (Flutter
|
|
111
|
+
// lock list has no stored token). Anyone else needs force-break.
|
|
112
|
+
if (!force && cur.holder !== holder) {
|
|
113
|
+
return { code: 409, error: 'token mismatch' };
|
|
114
|
+
}
|
|
115
|
+
delete data.locks[key];
|
|
116
|
+
await save();
|
|
117
|
+
return { code: 200 };
|
|
118
|
+
});
|
|
106
119
|
}
|
|
107
120
|
|
|
108
121
|
async function requireLock({ key, lockToken, writer, writerRole }) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
return withMutex(async () => {
|
|
123
|
+
await load();
|
|
124
|
+
purgeLocks();
|
|
125
|
+
if (!roleAtLeast(writerRole, 'editor')) {
|
|
126
|
+
return { code: 403, error: 'forbidden' };
|
|
127
|
+
}
|
|
128
|
+
let cur = data.locks[key];
|
|
129
|
+
if (!cur) {
|
|
130
|
+
const l = {
|
|
131
|
+
key, kind: 'auto', holder: writer, holderRole: writerRole,
|
|
132
|
+
token: lockToken || token(), exclusive: true,
|
|
133
|
+
acquiredAt: now(), expiresAt: now() + AUTO_CLAIM_MS, version: 1,
|
|
134
|
+
};
|
|
135
|
+
data.locks[key] = l;
|
|
136
|
+
await save();
|
|
137
|
+
return { code: 200, lock: { ...l } };
|
|
138
|
+
}
|
|
139
|
+
// Same holder may omit or present a stale token; they already own the lease.
|
|
140
|
+
if (cur.holder === writer) {
|
|
141
|
+
cur.expiresAt = now() + DEFAULT_TTL_MS;
|
|
142
|
+
await save();
|
|
143
|
+
return { code: 200, lock: { ...cur } };
|
|
144
|
+
}
|
|
123
145
|
return { code: 423, error: 'locked', lock: { ...cur, token: undefined } };
|
|
124
|
-
}
|
|
125
|
-
cur.expiresAt = now() + DEFAULT_TTL_MS;
|
|
126
|
-
await save();
|
|
127
|
-
return { code: 200, lock: { ...cur } };
|
|
146
|
+
});
|
|
128
147
|
}
|
|
129
148
|
|
|
130
149
|
async function listLocks() {
|
package/src/runtime/protocol.js
CHANGED
|
@@ -27,6 +27,9 @@ export function normalizeCommand(msg) {
|
|
|
27
27
|
requestId: msg.requestId || null,
|
|
28
28
|
allow: msg.allow,
|
|
29
29
|
always: msg.always,
|
|
30
|
+
// An immediate replacement turn keeps any server-side follow-ups. This
|
|
31
|
+
// is opt-in; the legacy Stop action continues to discard them.
|
|
32
|
+
preserveQueue: msg.preserveQueue === true,
|
|
30
33
|
cwd: msg.cwd || msg.options?.cwd || null,
|
|
31
34
|
};
|
|
32
35
|
}
|
|
@@ -42,6 +45,7 @@ export function normalizeCommand(msg) {
|
|
|
42
45
|
requestId: null,
|
|
43
46
|
allow: undefined,
|
|
44
47
|
always: false,
|
|
48
|
+
preserveQueue: false,
|
|
45
49
|
cwd: msg.options?.cwd || null,
|
|
46
50
|
};
|
|
47
51
|
}
|
|
@@ -57,6 +61,7 @@ export function normalizeCommand(msg) {
|
|
|
57
61
|
requestId: null,
|
|
58
62
|
allow: undefined,
|
|
59
63
|
always: false,
|
|
64
|
+
preserveQueue: false,
|
|
60
65
|
cwd: null,
|
|
61
66
|
};
|
|
62
67
|
}
|
|
@@ -72,6 +77,7 @@ export function normalizeCommand(msg) {
|
|
|
72
77
|
requestId: msg.requestId || msg.request_id,
|
|
73
78
|
allow: msg.allow !== false && msg.allow !== 0 && msg.allow !== '0',
|
|
74
79
|
always: !!msg.always,
|
|
80
|
+
preserveQueue: false,
|
|
75
81
|
cwd: null,
|
|
76
82
|
};
|
|
77
83
|
}
|
|
@@ -87,6 +93,7 @@ export function normalizeCommand(msg) {
|
|
|
87
93
|
requestId: null,
|
|
88
94
|
allow: undefined,
|
|
89
95
|
always: false,
|
|
96
|
+
preserveQueue: false,
|
|
90
97
|
cwd: msg.cwd || null,
|
|
91
98
|
};
|
|
92
99
|
}
|
|
@@ -102,6 +109,7 @@ export function normalizeCommand(msg) {
|
|
|
102
109
|
requestId: null,
|
|
103
110
|
allow: undefined,
|
|
104
111
|
always: false,
|
|
112
|
+
preserveQueue: false,
|
|
105
113
|
cwd: null,
|
|
106
114
|
};
|
|
107
115
|
}
|