@wipcomputer/wip-ldm-os 0.4.73-alpha.9 → 0.4.74
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/LICENSE +52 -0
- package/SKILL.md +8 -1
- package/bin/ldm.js +587 -82
- package/dist/bridge/chunk-3RG5ZIWI.js +10 -0
- package/dist/bridge/{chunk-LF7EMFBY.js → chunk-7NH6JBIO.js} +127 -49
- package/dist/bridge/cli.js +2 -1
- package/dist/bridge/core.d.ts +13 -1
- package/dist/bridge/core.js +4 -1
- package/dist/bridge/mcp-server.js +52 -7
- package/dist/bridge/openclaw.d.ts +5 -0
- package/dist/bridge/openclaw.js +11 -0
- package/docs/bridge/TECHNICAL.md +86 -0
- package/docs/doc-pipeline/README.md +74 -0
- package/docs/doc-pipeline/TECHNICAL.md +79 -0
- package/lib/deploy.mjs +175 -13
- package/lib/detect.mjs +20 -6
- package/package.json +2 -2
- package/shared/docs/README.md.tmpl +2 -2
- package/shared/docs/how-releases-work.md.tmpl +3 -1
- package/shared/docs/how-worktrees-work.md.tmpl +12 -7
- package/shared/rules/git-conventions.md +3 -3
- package/shared/rules/release-pipeline.md +1 -1
- package/shared/rules/security.md +1 -1
- package/shared/rules/workspace-boundaries.md +1 -1
- package/shared/rules/writing-style.md +1 -1
- package/shared/templates/claude-md-level1.md +7 -3
- package/src/bridge/core.ts +160 -56
- package/src/bridge/mcp-server.ts +93 -8
- package/src/bridge/openclaw.ts +14 -0
- package/src/hooks/inbox-check-hook.mjs +232 -0
- package/src/hooks/inbox-rewake-hook.mjs +388 -0
- package/src/hosted-mcp/.env.example +3 -0
- package/src/hosted-mcp/demo/agent.html +300 -0
- package/src/hosted-mcp/demo/agent.txt +84 -0
- package/src/hosted-mcp/demo/fallback.jpg +0 -0
- package/src/hosted-mcp/demo/footer.js +74 -0
- package/src/hosted-mcp/demo/index.html +1303 -0
- package/src/hosted-mcp/demo/login.html +548 -0
- package/src/hosted-mcp/demo/privacy.html +223 -0
- package/src/hosted-mcp/demo/sprites.jpg +0 -0
- package/src/hosted-mcp/demo/sprites.png +0 -0
- package/src/hosted-mcp/demo/tos.html +198 -0
- package/src/hosted-mcp/deploy.sh +70 -0
- package/src/hosted-mcp/ecosystem.config.cjs +14 -0
- package/src/hosted-mcp/inbox.mjs +64 -0
- package/src/hosted-mcp/legal/internet-services/terms/site.html +205 -0
- package/src/hosted-mcp/legal/privacy/en-ww/index.html +230 -0
- package/src/hosted-mcp/nginx/mcp-oauth.conf +98 -0
- package/src/hosted-mcp/nginx/mcp-server.conf +17 -0
- package/src/hosted-mcp/nginx/wip.computer.conf +45 -0
- package/src/hosted-mcp/package-lock.json +2092 -0
- package/src/hosted-mcp/package.json +23 -0
- package/src/hosted-mcp/prisma/migrations/20260406233014_init/migration.sql +68 -0
- package/src/hosted-mcp/prisma/migrations/migration_lock.toml +3 -0
- package/src/hosted-mcp/prisma/schema.prisma +57 -0
- package/src/hosted-mcp/prisma.config.ts +14 -0
- package/src/hosted-mcp/server.mjs +2093 -0
- package/src/hosted-mcp/shared/kaleidoscope.css +139 -0
- package/src/hosted-mcp/shared/kaleidoscope.js +192 -0
- package/src/hosted-mcp/tools.mjs +73 -0
- package/templates/hooks/pre-commit +5 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--bg: #FFFDF5;
|
|
5
|
+
--text: #1a1a1a;
|
|
6
|
+
--text-muted: #8a8580;
|
|
7
|
+
--lesa-bubble: #F0EDE6;
|
|
8
|
+
--user-bubble: #E8F0FE;
|
|
9
|
+
--accent: #0033FF;
|
|
10
|
+
--accent-hover: #0033FF;
|
|
11
|
+
--input-bg: #F5F3ED;
|
|
12
|
+
--input-border: #E0DDD6;
|
|
13
|
+
--font: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
html, body {
|
|
17
|
+
height: 100%;
|
|
18
|
+
font-family: var(--font);
|
|
19
|
+
background: var(--bg);
|
|
20
|
+
color: var(--text);
|
|
21
|
+
-webkit-text-size-adjust: 100%;
|
|
22
|
+
-webkit-font-smoothing: antialiased;
|
|
23
|
+
overflow: hidden;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* ── Login Page ── */
|
|
27
|
+
|
|
28
|
+
.login-page {
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-direction: column;
|
|
31
|
+
align-items: center;
|
|
32
|
+
justify-content: center;
|
|
33
|
+
height: 100vh;
|
|
34
|
+
height: 100dvh;
|
|
35
|
+
padding: 24px;
|
|
36
|
+
padding-top: calc(24px + env(safe-area-inset-top, 0px));
|
|
37
|
+
overflow-y: auto;
|
|
38
|
+
-webkit-overflow-scrolling: touch;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.login-card {
|
|
42
|
+
position: relative;
|
|
43
|
+
max-width: 380px;
|
|
44
|
+
width: 100%;
|
|
45
|
+
text-align: center;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.login-title {
|
|
49
|
+
font-size: 26px;
|
|
50
|
+
font-weight: 600;
|
|
51
|
+
letter-spacing: -0.02em;
|
|
52
|
+
margin-bottom: 8px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.login-byline {
|
|
56
|
+
font-size: 16px;
|
|
57
|
+
color: var(--text-muted);
|
|
58
|
+
margin-bottom: 8px;
|
|
59
|
+
letter-spacing: 0.2px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.login-tagline {
|
|
63
|
+
font-size: 17px;
|
|
64
|
+
color: var(--text-muted);
|
|
65
|
+
margin-bottom: 40px;
|
|
66
|
+
letter-spacing: 0.3px;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.login-buttons {
|
|
70
|
+
display: flex;
|
|
71
|
+
flex-direction: column;
|
|
72
|
+
gap: 12px;
|
|
73
|
+
margin-bottom: 16px;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.btn {
|
|
77
|
+
display: block;
|
|
78
|
+
width: 100%;
|
|
79
|
+
padding: 18px;
|
|
80
|
+
border: none;
|
|
81
|
+
border-radius: 12px;
|
|
82
|
+
font-size: 18px;
|
|
83
|
+
font-weight: 600;
|
|
84
|
+
font-family: var(--font);
|
|
85
|
+
cursor: pointer;
|
|
86
|
+
transition: background 0.15s, transform 0.1s;
|
|
87
|
+
-webkit-tap-highlight-color: transparent;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.btn:active {
|
|
91
|
+
transform: scale(0.98);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.btn-primary {
|
|
95
|
+
background: var(--accent);
|
|
96
|
+
color: white;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.btn-primary:hover {
|
|
100
|
+
background: var(--accent-hover);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.btn-secondary {
|
|
104
|
+
background: var(--input-bg);
|
|
105
|
+
color: var(--text);
|
|
106
|
+
border: 1px solid var(--input-border);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.btn-secondary:hover {
|
|
110
|
+
background: #EBE8E1;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.btn:disabled {
|
|
114
|
+
opacity: 0.5;
|
|
115
|
+
cursor: not-allowed;
|
|
116
|
+
transform: none;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.login-status {
|
|
120
|
+
margin-top: 16px;
|
|
121
|
+
font-size: 14px;
|
|
122
|
+
padding: 12px 16px;
|
|
123
|
+
border-radius: 10px;
|
|
124
|
+
display: none;
|
|
125
|
+
text-align: left;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.login-status.show { display: block; }
|
|
129
|
+
.login-status.loading { background: #E8EEFF; color: var(--accent); }
|
|
130
|
+
.login-status.error { background: #FFF0F0; color: #D32F2F; }
|
|
131
|
+
.login-status.success { background: #F0FFF4; color: #2E7D32; }
|
|
132
|
+
|
|
133
|
+
.login-footer {
|
|
134
|
+
margin-top: 48px;
|
|
135
|
+
font-size: 12px;
|
|
136
|
+
color: var(--text-muted);
|
|
137
|
+
letter-spacing: 0.2px;
|
|
138
|
+
}
|
|
139
|
+
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// Shared Kaleidoscope JS: WebAuthn helpers, sprite loading, status display
|
|
2
|
+
// Extracted verbatim from demo/index.html. DO NOT MODIFY separately.
|
|
3
|
+
// If the demo changes, re-extract.
|
|
4
|
+
|
|
5
|
+
// ── Base64url helpers ──
|
|
6
|
+
function b64urlToBytes(b64) {
|
|
7
|
+
var bin = atob(b64.replace(/-/g, '+').replace(/_/g, '/') + '=='.slice(0, (4 - b64.length % 4) % 4));
|
|
8
|
+
return Uint8Array.from(bin, function(c) { return c.charCodeAt(0); });
|
|
9
|
+
}
|
|
10
|
+
function bytesToB64url(buf) {
|
|
11
|
+
return btoa(String.fromCharCode.apply(null, new Uint8Array(buf)))
|
|
12
|
+
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// ── Status display (matches demo's .login-status CSS) ──
|
|
16
|
+
function setStatus(msg, type) {
|
|
17
|
+
var el = document.getElementById('loginStatus');
|
|
18
|
+
el.textContent = msg;
|
|
19
|
+
el.className = 'login-status show ' + type;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function clearLoginStatus() {
|
|
23
|
+
var el = document.getElementById('loginStatus');
|
|
24
|
+
el.style.transition = 'opacity 0.5s';
|
|
25
|
+
el.style.opacity = '0';
|
|
26
|
+
setTimeout(function() { el.className = 'login-status'; el.style.transition = ''; el.style.opacity = ''; }, 500);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ── Random kaleidoscope icon from sprite sheet ──
|
|
30
|
+
// sprites.png is 8 columns x 3 rows = 24 icons
|
|
31
|
+
var SPRITE_COLS = 8;
|
|
32
|
+
var SPRITE_ROWS = 3;
|
|
33
|
+
var SPRITE_TOTAL = SPRITE_COLS * SPRITE_ROWS;
|
|
34
|
+
|
|
35
|
+
function makeIconHTML(size, blue) {
|
|
36
|
+
var idx = Math.floor(Math.random() * SPRITE_TOTAL);
|
|
37
|
+
var col = idx % SPRITE_COLS;
|
|
38
|
+
var row = Math.floor(idx / SPRITE_COLS);
|
|
39
|
+
var bgPosX = (col / (SPRITE_COLS - 1)) * 100;
|
|
40
|
+
var bgPosY = (row / (SPRITE_ROWS - 1)) * 100;
|
|
41
|
+
if (blue) {
|
|
42
|
+
return '<div style="width:' + size + 'px;height:' + size + 'px;overflow:hidden;background:var(--accent);-webkit-mask-image:url(/demo/sprites.png);mask-image:url(/demo/sprites.png);-webkit-mask-size:' + (SPRITE_COLS * 100) + '% ' + (SPRITE_ROWS * 100) + '%;mask-size:' + (SPRITE_COLS * 100) + '% ' + (SPRITE_ROWS * 100) + '%;-webkit-mask-position:' + bgPosX + '% ' + bgPosY + '%;mask-position:' + bgPosX + '% ' + bgPosY + '%;"></div>';
|
|
43
|
+
}
|
|
44
|
+
return '<div style="width:' + size + 'px;height:' + size + 'px;overflow:hidden;"><div style="width:100%;height:100%;background:url(/demo/sprites.png);background-size:' + (SPRITE_COLS * 100) + '% ' + (SPRITE_ROWS * 100) + '%;background-position:' + bgPosX + '% ' + bgPosY + '%;"></div></div>';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function initLoginIcon(elementId, size) {
|
|
48
|
+
var el = document.getElementById(elementId);
|
|
49
|
+
if (!el) return;
|
|
50
|
+
el.innerHTML = makeIconHTML(size || 34, false);
|
|
51
|
+
// Rotate every 3s
|
|
52
|
+
var rotateIdx = Math.floor(Math.random() * SPRITE_TOTAL);
|
|
53
|
+
setInterval(function() {
|
|
54
|
+
rotateIdx = (rotateIdx + 1) % SPRITE_TOTAL;
|
|
55
|
+
var col = rotateIdx % SPRITE_COLS;
|
|
56
|
+
var row = Math.floor(rotateIdx / SPRITE_COLS);
|
|
57
|
+
var bgPosX = (col / (SPRITE_COLS - 1)) * 100;
|
|
58
|
+
var bgPosY = (row / (SPRITE_ROWS - 1)) * 100;
|
|
59
|
+
el.innerHTML = makeIconHTML(size || 34, false);
|
|
60
|
+
}, 3000);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── WebAuthn: Create Account ──
|
|
64
|
+
async function doCreateAccount(handleInputId, onSuccess) {
|
|
65
|
+
var username = (document.getElementById(handleInputId).value || '').trim().replace(/^@/, '').toLowerCase().replace(/[^a-z0-9\-]/g, '').slice(0, 30);
|
|
66
|
+
var btn = document.getElementById('createBtn');
|
|
67
|
+
btn.disabled = true;
|
|
68
|
+
setStatus('Preparing...', 'loading');
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
var optRes = await fetch('/webauthn/register-options', {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: { 'Content-Type': 'application/json' },
|
|
74
|
+
body: JSON.stringify({ agentName: username || undefined }),
|
|
75
|
+
});
|
|
76
|
+
var optData = await optRes.json();
|
|
77
|
+
var challengeId = optData.challengeId;
|
|
78
|
+
var options = optData.options;
|
|
79
|
+
if (!options) throw new Error('Server returned no options');
|
|
80
|
+
|
|
81
|
+
options.challenge = b64urlToBytes(options.challenge);
|
|
82
|
+
options.user.id = b64urlToBytes(options.user.id);
|
|
83
|
+
if (options.excludeCredentials) {
|
|
84
|
+
options.excludeCredentials = options.excludeCredentials.map(function(c) { return Object.assign({}, c, { id: b64urlToBytes(c.id) }); });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
setStatus('Waiting for biometric...', 'loading');
|
|
88
|
+
var credential = await navigator.credentials.create({ publicKey: options });
|
|
89
|
+
|
|
90
|
+
var reqBody = {
|
|
91
|
+
challengeId: challengeId,
|
|
92
|
+
credential: {
|
|
93
|
+
id: credential.id,
|
|
94
|
+
rawId: bytesToB64url(credential.rawId),
|
|
95
|
+
type: credential.type,
|
|
96
|
+
response: {
|
|
97
|
+
attestationObject: bytesToB64url(credential.response.attestationObject),
|
|
98
|
+
clientDataJSON: bytesToB64url(credential.response.clientDataJSON),
|
|
99
|
+
transports: credential.response.getTransports ? credential.response.getTransports() : [],
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
setStatus('Verifying...', 'loading');
|
|
105
|
+
var verRes = await fetch('/webauthn/register-verify', {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
headers: { 'Content-Type': 'application/json' },
|
|
108
|
+
body: JSON.stringify(reqBody),
|
|
109
|
+
});
|
|
110
|
+
var result = await verRes.json();
|
|
111
|
+
|
|
112
|
+
if (result.success) {
|
|
113
|
+
if (onSuccess) onSuccess(username || result.agentId, result);
|
|
114
|
+
} else {
|
|
115
|
+
setStatus(result.error || 'Registration failed', 'error');
|
|
116
|
+
btn.disabled = false;
|
|
117
|
+
}
|
|
118
|
+
} catch (err) {
|
|
119
|
+
if (err.name === 'NotAllowedError') {
|
|
120
|
+
setStatus('Cancelled. Try again when ready.', 'error');
|
|
121
|
+
} else {
|
|
122
|
+
setStatus('Error: ' + err.message, 'error');
|
|
123
|
+
}
|
|
124
|
+
btn.disabled = false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── WebAuthn: Sign In ──
|
|
129
|
+
async function doSignIn(onSuccess) {
|
|
130
|
+
var signInBtn = document.getElementById('signInBtn');
|
|
131
|
+
var createBtn = document.getElementById('createBtn');
|
|
132
|
+
if (signInBtn) signInBtn.style.pointerEvents = 'none';
|
|
133
|
+
if (createBtn) createBtn.disabled = true;
|
|
134
|
+
setStatus('Preparing...', 'loading');
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
var optRes = await fetch('/webauthn/auth-options', {
|
|
138
|
+
method: 'POST',
|
|
139
|
+
headers: { 'Content-Type': 'application/json' },
|
|
140
|
+
body: '{}',
|
|
141
|
+
});
|
|
142
|
+
var optData = await optRes.json();
|
|
143
|
+
var challengeId = optData.challengeId;
|
|
144
|
+
var options = optData.options;
|
|
145
|
+
if (!options) throw new Error('Server returned no options');
|
|
146
|
+
|
|
147
|
+
options.challenge = b64urlToBytes(options.challenge);
|
|
148
|
+
if (options.allowCredentials) {
|
|
149
|
+
options.allowCredentials = options.allowCredentials.map(function(c) { return Object.assign({}, c, { id: b64urlToBytes(c.id) }); });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
setStatus('Waiting for biometric...', 'loading');
|
|
153
|
+
var assertion = await navigator.credentials.get({ publicKey: options });
|
|
154
|
+
|
|
155
|
+
var reqBody = {
|
|
156
|
+
challengeId: challengeId,
|
|
157
|
+
credential: {
|
|
158
|
+
id: assertion.id, rawId: bytesToB64url(assertion.rawId), type: assertion.type,
|
|
159
|
+
response: {
|
|
160
|
+
authenticatorData: bytesToB64url(assertion.response.authenticatorData),
|
|
161
|
+
clientDataJSON: bytesToB64url(assertion.response.clientDataJSON),
|
|
162
|
+
signature: bytesToB64url(assertion.response.signature),
|
|
163
|
+
userHandle: assertion.response.userHandle ? bytesToB64url(assertion.response.userHandle) : null,
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
setStatus('Verifying...', 'loading');
|
|
169
|
+
var verRes = await fetch('/webauthn/auth-verify', {
|
|
170
|
+
method: 'POST',
|
|
171
|
+
headers: { 'Content-Type': 'application/json' },
|
|
172
|
+
body: JSON.stringify(reqBody),
|
|
173
|
+
});
|
|
174
|
+
var result = await verRes.json();
|
|
175
|
+
|
|
176
|
+
if (result.success) {
|
|
177
|
+
if (onSuccess) onSuccess(result.agentId, result);
|
|
178
|
+
} else {
|
|
179
|
+
setStatus(result.error || 'Authentication failed', 'error');
|
|
180
|
+
if (signInBtn) signInBtn.style.pointerEvents = 'auto';
|
|
181
|
+
if (createBtn) createBtn.disabled = false;
|
|
182
|
+
}
|
|
183
|
+
} catch (err) {
|
|
184
|
+
if (err.name === 'NotAllowedError') {
|
|
185
|
+
setStatus('Cancelled. Try again when ready.', 'error');
|
|
186
|
+
} else {
|
|
187
|
+
setStatus('Error: ' + err.message, 'error');
|
|
188
|
+
}
|
|
189
|
+
if (signInBtn) signInBtn.style.pointerEvents = 'auto';
|
|
190
|
+
if (createBtn) createBtn.disabled = false;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// tools.mjs: MCP tool definitions. Bridge (messaging) + placeholder memory tools.
|
|
2
|
+
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { pushMessage, getMessages, countPending } from "./inbox.mjs";
|
|
5
|
+
|
|
6
|
+
/** Register all tools on an McpServer instance. */
|
|
7
|
+
export function registerTools(server, getIdentity) {
|
|
8
|
+
|
|
9
|
+
server.registerTool("send_message", {
|
|
10
|
+
description: "Send a message to any agent. Stored in inbox until read. " +
|
|
11
|
+
"Target: 'agent', 'agent:session', 'agent:*' (all sessions), '*' (broadcast).",
|
|
12
|
+
inputSchema: {
|
|
13
|
+
to: z.string().describe("Recipient"),
|
|
14
|
+
body: z.string().describe("Message body"),
|
|
15
|
+
type: z.string().optional().default("chat").describe("chat, system, or task"),
|
|
16
|
+
},
|
|
17
|
+
}, async ({ to, body, type }) => {
|
|
18
|
+
const id = pushMessage({ from: getIdentity().agentId, to, body, type });
|
|
19
|
+
return { content: [{ type: "text", text: `Sent (id: ${id}) to ${to}` }] };
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
server.registerTool("check_inbox", {
|
|
23
|
+
description: "Check for pending messages. Returns unread messages and marks them read.",
|
|
24
|
+
inputSchema: {},
|
|
25
|
+
}, async () => {
|
|
26
|
+
const msgs = getMessages(getIdentity().agentId, true);
|
|
27
|
+
if (!msgs.length) return { content: [{ type: "text", text: "No pending messages." }] };
|
|
28
|
+
const text = msgs.map((m) => `**${m.from}** [${m.type}] (${m.timestamp}):\n${m.body}`).join("\n\n---\n\n");
|
|
29
|
+
return { content: [{ type: "text", text: `${msgs.length} message(s):\n\n${text}` }] };
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
server.registerTool("search_memory", {
|
|
33
|
+
description: "Search semantic memory (Crystal). Placeholder... coming soon.",
|
|
34
|
+
inputSchema: { query: z.string().describe("Search query") },
|
|
35
|
+
}, async ({ query }) => {
|
|
36
|
+
return { content: [{ type: "text", text: `Memory search coming soon. Query: "${query}"` }] };
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
server.registerTool("remember", {
|
|
40
|
+
description: "Store a fact in memory (Crystal). Placeholder... coming soon.",
|
|
41
|
+
inputSchema: {
|
|
42
|
+
text: z.string().describe("What to remember"),
|
|
43
|
+
tags: z.string().optional().describe("Comma-separated tags"),
|
|
44
|
+
},
|
|
45
|
+
}, async ({ text, tags }) => {
|
|
46
|
+
return { content: [{ type: "text", text: `Memory storage coming soon. Would remember: "${text}"${tags ? ` (tags: ${tags})` : ""}` }] };
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
server.registerTool("status", {
|
|
50
|
+
description: "Show connection info and pending message count.",
|
|
51
|
+
inputSchema: {},
|
|
52
|
+
}, async () => {
|
|
53
|
+
const { agentId, apiKey } = getIdentity();
|
|
54
|
+
const masked = apiKey.slice(0, 7) + "..." + apiKey.slice(-4);
|
|
55
|
+
return { content: [{ type: "text", text: `Agent: ${agentId}\nAPI key: ${masked}\nPending: ${countPending(agentId)}\nServer: wip.computer hosted MCP` }] };
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
server.registerTool("list_agents", {
|
|
59
|
+
description: "List all known agents on the Bridge. Shows who you can send messages to.",
|
|
60
|
+
inputSchema: {},
|
|
61
|
+
}, async () => {
|
|
62
|
+
// Read API_KEYS from server to find known agents
|
|
63
|
+
// For now, return hardcoded list plus any OAuth-registered agents
|
|
64
|
+
const known = [
|
|
65
|
+
{ id: "cc-mini", description: "Claude Code on Mac mini (CLI)" },
|
|
66
|
+
{ id: "lesa", description: "Lesa (OpenClaw agent on Mac mini)" },
|
|
67
|
+
{ id: "parker", description: "Parker (human, any device)" },
|
|
68
|
+
];
|
|
69
|
+
const text = known.map(a => `**${a.id}** ... ${a.description}`).join("\n");
|
|
70
|
+
return { content: [{ type: "text", text: `Known agents:\n\n${text}\n\nSend with: send_message(to: "agent-id", body: "your message")` }] };
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
}
|
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
branch=$(git branch --show-current 2>/dev/null)
|
|
9
9
|
|
|
10
|
+
# Allow first commit on empty repos (bootstrap)
|
|
11
|
+
if ! git rev-parse HEAD >/dev/null 2>&1; then
|
|
12
|
+
exit 0
|
|
13
|
+
fi
|
|
14
|
+
|
|
10
15
|
if [ "$branch" = "main" ] || [ "$branch" = "master" ]; then
|
|
11
16
|
echo ""
|
|
12
17
|
echo " BLOCKED: Cannot commit on $branch."
|