jiva-core 0.3.21 → 0.3.22

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.
@@ -0,0 +1,31 @@
1
+ name: Build and Publish to npm
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ build-and-publish:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ contents: read
12
+ steps:
13
+ - name: Checkout repository
14
+ uses: actions/checkout@v4
15
+
16
+ - name: Set up Node.js
17
+ uses: actions/setup-node@v4
18
+ with:
19
+ node-version: '20'
20
+ registry-url: 'https://registry.npmjs.org'
21
+
22
+ - name: Install dependencies
23
+ run: npm ci --ignore-scripts
24
+
25
+ - name: Build
26
+ run: npm run build
27
+
28
+ - name: Publish to npm
29
+ run: npm publish --access public
30
+ env:
31
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jiva-core",
3
- "version": "0.3.21",
3
+ "version": "0.3.22",
4
4
  "description": "Versatile autonomous AI agent with three-agent architecture (Manager, Worker, Client) powered by gpt-oss-120b. Adaptive validation, full MCP support, and intelligent quality control.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -89,5 +89,9 @@
89
89
  },
90
90
  "engines": {
91
91
  "node": ">=20.0.0"
92
+ },
93
+ "overrides": {
94
+ "glob": "^13.0.0",
95
+ "tr46": "^3.0.0"
92
96
  }
93
97
  }
@@ -1,18 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(npm run build:*)",
5
- "Bash(npm install:*)",
6
- "Bash(node:*)",
7
- "Bash(cat:*)",
8
- "Bash(chmod:*)",
9
- "Bash(jiva:*)",
10
- "Bash(npm config:*)",
11
- "Bash(echo:*)",
12
- "Bash(/Users/abidev/.npm-global/bin/jiva:*)",
13
- "Bash(npm info:*)"
14
- ],
15
- "deny": [],
16
- "ask": []
17
- }
18
- }
@@ -1,4 +0,0 @@
1
- import { Express } from 'express';
2
- import { SessionManager } from '../session-manager.js';
3
- export declare function setupUIRoutes(app: Express, sessionManager: SessionManager): void;
4
- //# sourceMappingURL=ui.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../../../src/interfaces/http/routes/ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAA6B,MAAM,SAAS,CAAC;AAK7D,OAAO,EAAE,cAAc,EAAkB,MAAM,uBAAuB,CAAC;AAavE,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,cAAc,EAAE,cAAc,GAAG,IAAI,CA6JhF"}
@@ -1,213 +0,0 @@
1
- import { Router } from 'express';
2
- import { mkdir, mkdtemp, readFile, rm, writeFile, cp, readdir, stat } from 'fs/promises';
3
- import os from 'os';
4
- import path from 'path';
5
- import unzipper from 'unzipper';
6
- import { logger } from '../../../utils/logger.js';
7
- export function setupUIRoutes(app, sessionManager) {
8
- const router = Router();
9
- const requireSession = (req, res) => {
10
- if (!req.auth) {
11
- res.status(401).json({ success: false, error: 'Authentication required' });
12
- return null;
13
- }
14
- const { tenantId, sessionId } = req.auth;
15
- const session = sessionManager.getSessionContext(tenantId, sessionId);
16
- if (!session) {
17
- res.status(404).json({ success: false, error: 'Session not found' });
18
- return null;
19
- }
20
- sessionManager.setStorageContext(tenantId, sessionId);
21
- return session;
22
- };
23
- router.get('/session', (req, res) => {
24
- const session = requireSession(req, res);
25
- if (!session)
26
- return;
27
- res.json({
28
- success: true,
29
- session: session.info,
30
- workspace: {
31
- dir: session.workspace.getWorkspaceDir(),
32
- hasDirective: session.workspace.hasDirective(),
33
- },
34
- persona: {
35
- active: session.personaManager.getActivePersona()?.manifest.name || null,
36
- },
37
- });
38
- });
39
- router.get('/personas', (req, res) => {
40
- const session = requireSession(req, res);
41
- if (!session)
42
- return;
43
- const personas = session.personaManager.getPersonas().map((p) => ({
44
- name: p.manifest.name,
45
- description: p.manifest.description,
46
- version: p.manifest.version,
47
- active: p.active,
48
- skills: p.skills.length,
49
- mcpServers: Object.keys(p.mcpServers || {}).length,
50
- }));
51
- res.json({
52
- success: true,
53
- personas,
54
- active: session.personaManager.getActivePersona()?.manifest.name || null,
55
- });
56
- });
57
- router.post('/personas/activate', async (req, res) => {
58
- const session = requireSession(req, res);
59
- if (!session)
60
- return;
61
- const { name } = req.body || {};
62
- if (!name || typeof name !== 'string') {
63
- res.status(400).json({ success: false, error: 'Persona name is required' });
64
- return;
65
- }
66
- try {
67
- const activated = await session.personaManager.activatePersona(name);
68
- if (!activated) {
69
- res.status(404).json({ success: false, error: 'Persona not found' });
70
- return;
71
- }
72
- res.json({ success: true, message: `Activated persona: ${name}` });
73
- }
74
- catch (error) {
75
- logger.error('[UI] Failed to activate persona:', error);
76
- res.status(500).json({ success: false, error: 'Failed to activate persona' });
77
- }
78
- });
79
- router.post('/personas/install', async (req, res) => {
80
- const session = requireSession(req, res);
81
- if (!session)
82
- return;
83
- const payload = req.body;
84
- if (!payload?.filename || !payload?.content) {
85
- res.status(400).json({ success: false, error: 'File upload requires filename and content' });
86
- return;
87
- }
88
- try {
89
- await installPersonaArchive(payload.content, session.workspace.getWorkspaceDir());
90
- await session.personaManager.refresh();
91
- res.json({ success: true, message: 'Persona bundle installed into .jiva/personas' });
92
- }
93
- catch (error) {
94
- logger.error('[UI] Persona install failed:', error);
95
- res.status(500).json({ success: false, error: 'Failed to install persona bundle' });
96
- }
97
- });
98
- router.get('/mcp', async (req, res) => {
99
- const session = requireSession(req, res);
100
- if (!session)
101
- return;
102
- const storage = sessionManager.getStorageProvider();
103
- const stored = await storage.getConfig('mcpServers');
104
- res.json({
105
- success: true,
106
- servers: Array.isArray(stored) ? stored : [],
107
- status: session.mcpManager.getServerStatus(),
108
- });
109
- });
110
- router.post('/mcp', async (req, res) => {
111
- const session = requireSession(req, res);
112
- if (!session)
113
- return;
114
- const { servers } = req.body || {};
115
- if (!Array.isArray(servers)) {
116
- res.status(400).json({ success: false, error: 'Servers must be an array' });
117
- return;
118
- }
119
- try {
120
- const storage = sessionManager.getStorageProvider();
121
- await storage.setConfig('mcpServers', servers);
122
- res.json({
123
- success: true,
124
- message: 'MCP servers saved. Restart the session to apply changes.',
125
- });
126
- }
127
- catch (error) {
128
- logger.error('[UI] MCP update failed:', error);
129
- res.status(500).json({ success: false, error: 'Failed to persist MCP servers' });
130
- }
131
- });
132
- router.post('/workspace/upload', async (req, res) => {
133
- const session = requireSession(req, res);
134
- if (!session)
135
- return;
136
- const payload = req.body;
137
- if (!payload?.targetPath || !payload?.content) {
138
- res.status(400).json({ success: false, error: 'targetPath and content are required' });
139
- return;
140
- }
141
- try {
142
- await saveWorkspaceFile(payload.targetPath, payload.content, session.workspace.getWorkspaceDir());
143
- res.json({ success: true, message: 'File uploaded into workspace' });
144
- }
145
- catch (error) {
146
- logger.error('[UI] Workspace upload failed:', error);
147
- res.status(500).json({ success: false, error: 'Failed to write workspace file' });
148
- }
149
- });
150
- app.use('/api/ui', router);
151
- }
152
- async function installPersonaArchive(content, workspaceDir) {
153
- const buffer = Buffer.from(content, 'base64');
154
- const tempDir = await mkdtemp(path.join(os.tmpdir(), 'jiva-persona-'));
155
- try {
156
- const archive = await unzipper.Open.buffer(buffer);
157
- await archive.extract({ path: tempDir, concurrency: 5 });
158
- const entries = await readdir(tempDir);
159
- let extractedRoot = tempDir;
160
- if (entries.length === 1) {
161
- const singlePath = path.join(tempDir, entries[0]);
162
- if ((await stat(singlePath)).isDirectory()) {
163
- extractedRoot = singlePath;
164
- }
165
- }
166
- const manifestPath = await findPersonaManifest(extractedRoot);
167
- const manifestContent = await readFile(manifestPath, 'utf-8');
168
- const manifest = JSON.parse(manifestContent);
169
- const personaName = manifest.name || path.basename(manifestPath, path.extname(manifestPath));
170
- const sanitizedName = personaName
171
- .toString()
172
- .trim()
173
- .toLowerCase()
174
- .replace(/[^a-z0-9-_]/g, '-');
175
- const personasDir = path.join(workspaceDir, '.jiva', 'personas');
176
- await mkdir(personasDir, { recursive: true });
177
- const targetDir = path.join(personasDir, sanitizedName || Date.now().toString());
178
- await rm(targetDir, { recursive: true, force: true });
179
- await cp(extractedRoot, targetDir, { recursive: true });
180
- }
181
- finally {
182
- await rm(tempDir, { recursive: true, force: true });
183
- }
184
- }
185
- async function findPersonaManifest(baseDir) {
186
- const candidates = [
187
- path.join(baseDir, '.jiva-plugin', 'plugin.json'),
188
- path.join(baseDir, '.claude-plugin', 'plugin.json'),
189
- ];
190
- for (const candidate of candidates) {
191
- try {
192
- await stat(candidate);
193
- return candidate;
194
- }
195
- catch (error) {
196
- if (error.code !== 'ENOENT') {
197
- throw error;
198
- }
199
- }
200
- }
201
- throw new Error('Persona manifest not found in archive');
202
- }
203
- async function saveWorkspaceFile(targetPath, content, workspaceDir) {
204
- const normalizedWorkspaceDir = path.resolve(workspaceDir);
205
- const normalizedTarget = path.normalize(targetPath).replace(/^\//, '');
206
- const absolutePath = path.resolve(normalizedWorkspaceDir, normalizedTarget);
207
- if (!absolutePath.startsWith(normalizedWorkspaceDir)) {
208
- throw new Error('Target path is outside the workspace');
209
- }
210
- await mkdir(path.dirname(absolutePath), { recursive: true });
211
- await writeFile(absolutePath, Buffer.from(content, 'base64'));
212
- }
213
- //# sourceMappingURL=ui.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ui.js","sourceRoot":"","sources":["../../../../src/interfaces/http/routes/ui.ts"],"names":[],"mappings":"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"}
@@ -1,361 +0,0 @@
1
- const state = {
2
- theme: localStorage.getItem('jiva-theme') || getPreferredTheme(),
3
- messages: [],
4
- steps: [],
5
- status: 'Ready',
6
- };
7
-
8
- const dom = {
9
- status: document.getElementById('statusMessage'),
10
- conversation: document.getElementById('conversation'),
11
- agentSteps: document.getElementById('agentStepsList'),
12
- chatForm: document.getElementById('chatForm'),
13
- chatInput: document.getElementById('chatInput'),
14
- personaList: document.getElementById('personaList'),
15
- personaSelect: document.getElementById('personaSelect'),
16
- personaUpload: document.getElementById('personaUpload'),
17
- personaInstallBtn: document.getElementById('personaInstallBtn'),
18
- personaInstallStatus: document.getElementById('personaInstallStatus'),
19
- personaActivateForm: document.getElementById('personaActivateForm'),
20
- mcpEditor: document.getElementById('mcpEditor'),
21
- saveMcp: document.getElementById('saveMcp'),
22
- mcpStatus: document.getElementById('mcpStatus'),
23
- workspaceForm: document.getElementById('workspaceUploadForm'),
24
- workspacePath: document.getElementById('workspacePath'),
25
- workspaceFile: document.getElementById('workspaceFile'),
26
- uploadStatus: document.getElementById('uploadStatus'),
27
- themeToggle: document.getElementById('themeToggle'),
28
- refreshSession: document.getElementById('refreshSession'),
29
- };
30
-
31
- document.documentElement.setAttribute('data-theme', state.theme);
32
- dom.themeToggle.textContent = state.theme === 'dark' ? 'Light' : 'Dark';
33
-
34
- dom.themeToggle.addEventListener('click', () => {
35
- state.theme = state.theme === 'light' ? 'dark' : 'light';
36
- document.documentElement.setAttribute('data-theme', state.theme);
37
- localStorage.setItem('jiva-theme', state.theme);
38
- dom.themeToggle.textContent = state.theme === 'dark' ? 'Light' : 'Dark';
39
- });
40
-
41
- dom.chatForm.addEventListener('submit', async (event) => {
42
- event.preventDefault();
43
- const message = dom.chatInput.value.trim();
44
- if (!message) return;
45
-
46
- state.messages.push({ role: 'user', content: message });
47
- dom.chatInput.value = '';
48
- renderConversation();
49
- setStatus('Agent is thinking...');
50
- setSteps([]);
51
- await streamChat(message);
52
- });
53
-
54
- dom.personaActivateForm?.addEventListener('submit', async (event) => {
55
- event.preventDefault();
56
- const selected = dom.personaSelect.value;
57
- if (!selected) return;
58
- await activatePersona(selected);
59
- });
60
-
61
- dom.personaInstallBtn.addEventListener('click', async () => {
62
- const file = dom.personaUpload.files?.[0];
63
- if (!file) {
64
- updateStatus(dom.personaInstallStatus, 'Select a zip bundle first.', true);
65
- return;
66
- }
67
- const base64 = await readFileAsBase64(file);
68
- await installPersonaBundle(file.name, base64);
69
- });
70
-
71
- dom.saveMcp.addEventListener('click', async () => {
72
- await persistMcpConfig();
73
- });
74
-
75
- dom.workspaceForm.addEventListener('submit', async (event) => {
76
- event.preventDefault();
77
- const file = dom.workspaceFile.files?.[0];
78
- if (!file) {
79
- updateStatus(dom.uploadStatus, 'Select a file to upload.', true);
80
- return;
81
- }
82
- const base64 = await readFileAsBase64(file);
83
- await uploadWorkspaceFile(dom.workspacePath.value.trim(), base64);
84
- });
85
-
86
- dom.refreshSession.addEventListener('click', () => {
87
- loadSessionInfo();
88
- loadPersonas();
89
- });
90
-
91
- async function init() {
92
- await Promise.all([loadSessionInfo(), loadPersonas(), loadMcpConfig()]);
93
- }
94
-
95
- function renderConversation() {
96
- dom.conversation.innerHTML = state.messages
97
- .map((message) => {
98
- const className = message.role === 'assistant' ? 'assistant' : 'user';
99
- return `<div class="message ${className}">${sanitize(message.content)}</div>`;
100
- })
101
- .join('');
102
- dom.conversation.scrollTop = dom.conversation.scrollHeight;
103
- }
104
-
105
- function sanitize(value) {
106
- const div = document.createElement('div');
107
- div.textContent = value;
108
- return div.innerHTML;
109
- }
110
-
111
- function setStatus(text) {
112
- state.status = text;
113
- dom.status.textContent = text;
114
- }
115
-
116
- function setSteps(subtasks) {
117
- state.steps = subtasks.map((text, index) => ({
118
- text,
119
- status: index === 0 ? 'active' : 'pending',
120
- }));
121
- renderSteps();
122
- }
123
-
124
- function markStepsDone() {
125
- state.steps = state.steps.map((step) => ({ ...step, status: 'done' }));
126
- renderSteps();
127
- }
128
-
129
- function renderSteps() {
130
- if (!dom.agentSteps) return;
131
- dom.agentSteps.innerHTML = state.steps
132
- .map((step) => {
133
- const classes = ['step-item', step.status];
134
- return `<li class="${classes.join(' ')}">${sanitize(step.text)}</li>`;
135
- })
136
- .join('');
137
- }
138
-
139
- function parseSSE(chunk) {
140
- const lines = chunk.split('\n');
141
- let event = 'message';
142
- let data = '';
143
- for (const line of lines) {
144
- if (line.startsWith('event:')) {
145
- event = line.replace('event:', '').trim();
146
- }
147
- if (line.startsWith('data:')) {
148
- data += line.replace('data:', '').trim();
149
- }
150
- }
151
- return { event, data: data ? JSON.parse(data) : null };
152
- }
153
-
154
- async function streamChat(message) {
155
- const response = await fetch('/api/chat/stream', {
156
- method: 'POST',
157
- headers: { 'Content-Type': 'application/json' },
158
- body: JSON.stringify({ message }),
159
- });
160
-
161
- if (!response.ok || !response.body) {
162
- setStatus('Failed to stream response');
163
- return;
164
- }
165
-
166
- const reader = response.body.getReader();
167
- const decoder = new TextDecoder();
168
- let buffer = '';
169
-
170
- try {
171
- while (true) {
172
- const { value, done } = await reader.read();
173
- if (value) {
174
- buffer += decoder.decode(value, { stream: true });
175
- }
176
- let boundary;
177
- while ((boundary = buffer.indexOf('\n\n')) !== -1) {
178
- const chunk = buffer.slice(0, boundary);
179
- buffer = buffer.slice(boundary + 2);
180
- const event = parseSSE(chunk);
181
- handleSSEEvent(event);
182
- }
183
- if (done) break;
184
- }
185
- if (buffer.trim()) {
186
- const event = parseSSE(buffer);
187
- handleSSEEvent(event);
188
- }
189
- } catch (error) {
190
- setStatus('Streaming interrupted');
191
- }
192
- }
193
-
194
- function handleSSEEvent({ event, data }) {
195
- if (event === 'status') {
196
- setStatus(data?.message || 'Processing...');
197
- }
198
- if (event === 'response') {
199
- if (data?.plan?.subtasks) {
200
- setSteps(data.plan.subtasks);
201
- }
202
- const assistantText = data?.content || 'Response ready';
203
- state.messages.push({ role: 'assistant', content: assistantText });
204
- renderConversation();
205
- setStatus('Response ready');
206
- }
207
- if (event === 'done') {
208
- markStepsDone();
209
- }
210
- if (event === 'error') {
211
- setStatus(data?.message || 'Agent error');
212
- }
213
- }
214
-
215
- async function loadSessionInfo() {
216
- try {
217
- const response = await fetch('/api/session');
218
- if (!response.ok) {
219
- throw new Error('Session data unavailable');
220
- }
221
- const payload = await response.json();
222
- const label = payload.session?.status === 'active' ? 'Ready' : 'Initializing';
223
- setStatus(`Session ${label}`);
224
- } catch (error) {
225
- setStatus('Unable to read session');
226
- }
227
- }
228
-
229
- async function loadPersonas() {
230
- try {
231
- const response = await fetch('/api/ui/personas');
232
- if (!response.ok) throw new Error('Persona list failed');
233
- const payload = await response.json();
234
- const personas = Array.isArray(payload.personas) ? payload.personas : [];
235
- dom.personaList.innerHTML = personas
236
- .map((persona) => {
237
- const active = persona.active ? 'active' : '';
238
- return `<div class="persona-row ${active}">
239
- <strong>${persona.name}</strong>
240
- <p class="subtle">${persona.description}</p>
241
- <span class="muted">v${persona.version} • ${persona.skills} skills</span>
242
- </div>`;
243
- })
244
- .join('');
245
- dom.personaSelect.innerHTML = personas
246
- .map((persona) => `<option value="${persona.name}">${persona.name}</option>`)
247
- .join('');
248
- } catch (error) {
249
- console.error(error);
250
- }
251
- }
252
-
253
- async function activatePersona(name) {
254
- try {
255
- const response = await fetch('/api/ui/personas/activate', {
256
- method: 'POST',
257
- headers: { 'Content-Type': 'application/json' },
258
- body: JSON.stringify({ name }),
259
- });
260
- const payload = await response.json();
261
- if (!payload.success) {
262
- throw new Error(payload.error || 'Activation failed');
263
- }
264
- updateStatus(dom.personaInstallStatus, payload.message, false);
265
- await loadPersonas();
266
- dom.personaUpload.value = '';
267
- } catch (error) {
268
- updateStatus(dom.personaInstallStatus, error.message, true);
269
- }
270
- }
271
-
272
- async function installPersonaBundle(filename, base64) {
273
- try {
274
- const response = await fetch('/api/ui/personas/install', {
275
- method: 'POST',
276
- headers: { 'Content-Type': 'application/json' },
277
- body: JSON.stringify({ filename, content: base64 }),
278
- });
279
- const payload = await response.json();
280
- if (!payload.success) {
281
- throw new Error(payload.error || 'Installation failed');
282
- }
283
- updateStatus(dom.personaInstallStatus, payload.message, false);
284
- await loadPersonas();
285
- } catch (error) {
286
- updateStatus(dom.personaInstallStatus, error.message, true);
287
- }
288
- }
289
-
290
- async function loadMcpConfig() {
291
- try {
292
- const response = await fetch('/api/ui/mcp');
293
- if (!response.ok) throw new Error('MCP fetch failed');
294
- const payload = await response.json();
295
- dom.mcpEditor.value = JSON.stringify(payload.servers, null, 2);
296
- dom.mcpStatus.textContent = `${payload.status.length} server(s) registered`;
297
- } catch (error) {
298
- dom.mcpStatus.textContent = 'Unable to load MCP servers';
299
- }
300
- }
301
-
302
- async function persistMcpConfig() {
303
- try {
304
- const payload = JSON.parse(dom.mcpEditor.value || '[]');
305
- const response = await fetch('/api/ui/mcp', {
306
- method: 'POST',
307
- headers: { 'Content-Type': 'application/json' },
308
- body: JSON.stringify({ servers: payload }),
309
- });
310
- const body = await response.json();
311
- if (!body.success) throw new Error(body.error || 'Save failed');
312
- updateStatus(dom.mcpStatus, body.message, false);
313
- } catch (error) {
314
- updateStatus(dom.mcpStatus, error.message, true);
315
- }
316
- }
317
-
318
- async function uploadWorkspaceFile(targetPath, base64) {
319
- try {
320
- const response = await fetch('/api/ui/workspace/upload', {
321
- method: 'POST',
322
- headers: { 'Content-Type': 'application/json' },
323
- body: JSON.stringify({ targetPath, content: base64 }),
324
- });
325
- const payload = await response.json();
326
- if (!payload.success) throw new Error(payload.error || 'Upload failed');
327
- updateStatus(dom.uploadStatus, payload.message, false);
328
- dom.workspaceForm.reset();
329
- } catch (error) {
330
- updateStatus(dom.uploadStatus, error.message, true);
331
- }
332
- }
333
-
334
- function updateStatus(element, text, isError = false) {
335
- if (!element) return;
336
- element.textContent = text;
337
- element.style.color = isError ? '#f43f5e' : '';
338
- }
339
-
340
- function readFileAsBase64(file) {
341
- return new Promise((resolve, reject) => {
342
- const reader = new FileReader();
343
- reader.onload = () => {
344
- const dataUrl = reader.result;
345
- if (typeof dataUrl !== 'string') return reject(new Error('Unable to read file'));
346
- const base64 = dataUrl.split(',')[1];
347
- resolve(base64);
348
- };
349
- reader.onerror = reject;
350
- reader.readAsDataURL(file);
351
- });
352
- }
353
-
354
- function getPreferredTheme() {
355
- if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
356
- return 'dark';
357
- }
358
- return 'light';
359
- }
360
-
361
- document.addEventListener('DOMContentLoaded', init);
@@ -1,122 +0,0 @@
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" />
6
- <title>Jiva Control Center</title>
7
- <link rel="preconnect" href="https://fonts.googleapis.com" />
8
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
- <link
10
- href="https://fonts.googleapis.com/css2?family=Lato:wght@300;400&display=swap"
11
- rel="stylesheet"
12
- />
13
- <link rel="stylesheet" href="styles.css" />
14
- </head>
15
- <body>
16
- <div class="app-shell" data-theme="light">
17
- <header class="hero">
18
- <div class="logo-block">
19
- <svg class="logo-mark" viewBox="0 0 120 64" aria-hidden="true">
20
- <defs>
21
- <linearGradient id="accentGradient" x1="0%" y1="0%" x2="100%" y2="100%">
22
- <stop offset="0%" stop-color="#4c6ef5" />
23
- <stop offset="100%" stop-color="#a855f7" />
24
- </linearGradient>
25
- </defs>
26
- <path
27
- d="M18 32c0-11 13-25 30-25s30 14 30 25-13 25-30 25-30-14-30-25z"
28
- stroke="url(#accentGradient)"
29
- stroke-width="8"
30
- fill="none"
31
- stroke-linecap="round"
32
- />
33
- <path
34
- d="M72 32c0 11-13 25-30 25S12 43 12 32c0-11 13-25 30-25s30 14 30 25z"
35
- stroke="url(#accentGradient)"
36
- stroke-width="8"
37
- fill="none"
38
- stroke-linecap="round"
39
- />
40
- </svg>
41
- <div>
42
- <p class="eyebrow">Jiva</p>
43
- <h1>Agent Cockpit</h1>
44
- </div>
45
- </div>
46
- <button id="themeToggle" class="theme-toggle" type="button">Dark</button>
47
- </header>
48
-
49
- <main class="panel-grid">
50
- <section class="chat-panel">
51
- <div class="panel-header">
52
- <div>
53
- <p class="label">Agent status</p>
54
- <p id="statusMessage" class="status-pill">Ready</p>
55
- </div>
56
- <button id="refreshSession" class="ghost-btn" type="button">Refresh Session</button>
57
- </div>
58
-
59
- <details class="agent-steps" open>
60
- <summary>Agent Steps</summary>
61
- <ul id="agentStepsList"></ul>
62
- </details>
63
-
64
- <div id="conversation" class="conversation"></div>
65
-
66
- <form id="chatForm" class="chat-form" autocomplete="off">
67
- <textarea id="chatInput" rows="4" placeholder="Ask Jiva anything..." required></textarea>
68
- <div class="form-actions">
69
- <button type="submit" class="primary">Send Message</button>
70
- <p class="hint">Responses stay deterministic with the latest 0.3.2 logic.</p>
71
- </div>
72
- </form>
73
- </section>
74
-
75
- <aside class="control-panel">
76
- <div class="card">
77
- <header>
78
- <h2>Personas</h2>
79
- <p class="subtle">Activate or install new personas</p>
80
- </header>
81
- <div id="personaList" class="persona-list"></div>
82
- <form id="personaActivateForm" class="stacked">
83
- <select id="personaSelect"></select>
84
- <button type="submit" class="primary">Activate persona</button>
85
- </form>
86
- <div class="upload-field">
87
- <label for="personaUpload" class="label">Install persona / Claude plugin</label>
88
- <input id="personaUpload" type="file" accept=".zip" />
89
- <button id="personaInstallBtn" type="button" class="ghost-btn">Install bundle</button>
90
- <p id="personaInstallStatus" class="subtle"></p>
91
- </div>
92
- </div>
93
-
94
- <div class="card">
95
- <header>
96
- <h2>MCP Servers</h2>
97
- <p class="subtle">Configure tooling endpoints</p>
98
- </header>
99
- <textarea id="mcpEditor" rows="6" placeholder='[{"name":"filesystem","command":"npx","args":["-y","@modelcontextprotocol/server-filesystem","/Users"],"enabled":true}]'></textarea>
100
- <button id="saveMcp" type="button" class="primary">Save MCP config</button>
101
- <p id="mcpStatus" class="subtle"></p>
102
- </div>
103
-
104
- <div class="card">
105
- <header>
106
- <h2>Workspace Upload</h2>
107
- <p class="subtle">Drop a file straight into .jiva workspace</p>
108
- </header>
109
- <form id="workspaceUploadForm" class="stacked">
110
- <input id="workspacePath" type="text" placeholder="relative/path/to/file.txt" value="uploads/notes.txt" required />
111
- <input id="workspaceFile" type="file" required />
112
- <button type="submit" class="primary">Upload to workspace</button>
113
- <p id="uploadStatus" class="subtle"></p>
114
- </form>
115
- </div>
116
- </aside>
117
- </main>
118
- </div>
119
-
120
- <script type="module" src="app.js"></script>
121
- </body>
122
- </html>
@@ -1,319 +0,0 @@
1
- :root {
2
- --page-bg: #f5f6fb;
3
- --panel-bg: #ffffff;
4
- --card-bg: rgba(255, 255, 255, 0.85);
5
- --text: #0f172a;
6
- --muted: #52606d;
7
- --border: rgba(15, 23, 42, 0.08);
8
- --accent-start: #4c6ef5;
9
- --accent-end: #a855f7;
10
- --shadow: 0 20px 45px rgba(15, 23, 42, 0.15);
11
- }
12
-
13
- [data-theme='dark'] {
14
- --page-bg: #05060d;
15
- --panel-bg: rgba(10, 13, 33, 0.92);
16
- --card-bg: rgba(15, 19, 44, 0.88);
17
- --text: #f8fafc;
18
- --muted: #cbd5f5;
19
- --border: rgba(255, 255, 255, 0.1);
20
- --shadow: 0 18px 35px rgba(10, 14, 33, 0.55);
21
- }
22
-
23
- * {
24
- box-sizing: border-box;
25
- }
26
-
27
- body {
28
- margin: 0;
29
- min-height: 100vh;
30
- font-family: 'Lato', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
31
- background: var(--page-bg);
32
- color: var(--text);
33
- background-image: radial-gradient(circle at top right, rgba(76, 110, 245, 0.2), transparent 40%),
34
- radial-gradient(circle at 20% 20%, rgba(168, 85, 247, 0.2), transparent 35%);
35
- }
36
-
37
- .app-shell {
38
- min-height: 100vh;
39
- padding: 2rem clamp(1rem, 3vw, 3rem);
40
- max-width: 1400px;
41
- margin: 0 auto;
42
- }
43
-
44
- .hero {
45
- display: flex;
46
- justify-content: space-between;
47
- align-items: center;
48
- margin-bottom: 1.5rem;
49
- gap: 1rem;
50
- }
51
-
52
- .logo-block {
53
- display: flex;
54
- align-items: center;
55
- gap: 0.85rem;
56
- }
57
-
58
- .logo-mark {
59
- width: 64px;
60
- height: 40px;
61
- }
62
-
63
- .logo-block h1 {
64
- margin: 0;
65
- font-size: clamp(1.5rem, 2.2vw, 2.5rem);
66
- letter-spacing: 0.05em;
67
- }
68
-
69
- .hero .eyebrow {
70
- font-size: 0.85rem;
71
- margin: 0;
72
- color: var(--muted);
73
- }
74
-
75
- .theme-toggle {
76
- border: none;
77
- background: linear-gradient(135deg, var(--accent-start), var(--accent-end));
78
- color: #fff;
79
- padding: 0.8rem 1.3rem;
80
- border-radius: 999px;
81
- cursor: pointer;
82
- font-weight: 600;
83
- font-size: 0.9rem;
84
- transition: transform 0.2s ease;
85
- box-shadow: var(--shadow);
86
- }
87
-
88
- .theme-toggle:active {
89
- transform: scale(0.98);
90
- }
91
-
92
- .panel-grid {
93
- display: grid;
94
- grid-template-columns: minmax(0, 2fr) 1fr;
95
- gap: 1.5rem;
96
- }
97
-
98
- .chat-panel,
99
- .control-panel .card {
100
- background: var(--panel-bg);
101
- border-radius: 1.25rem;
102
- padding: 1.5rem;
103
- box-shadow: var(--shadow);
104
- border: 1px solid var(--border);
105
- }
106
-
107
- .chat-panel {
108
- display: flex;
109
- flex-direction: column;
110
- gap: 1rem;
111
- }
112
-
113
- .chat-panel .panel-header {
114
- display: flex;
115
- align-items: center;
116
- justify-content: space-between;
117
- }
118
-
119
- .status-pill {
120
- background: rgba(76, 110, 245, 0.15);
121
- color: #1d2cfb;
122
- padding: 0.35rem 0.9rem;
123
- border-radius: 999px;
124
- font-size: 0.85rem;
125
- margin: 0;
126
- }
127
-
128
- .ghost-btn {
129
- border-radius: 999px;
130
- border: 1px solid var(--border);
131
- background: transparent;
132
- padding: 0.7rem 1.2rem;
133
- cursor: pointer;
134
- color: var(--text);
135
- }
136
-
137
- .conversation {
138
- flex: 1;
139
- min-height: 280px;
140
- display: flex;
141
- flex-direction: column;
142
- gap: 0.8rem;
143
- overflow-y: auto;
144
- padding-right: 0.2rem;
145
- }
146
-
147
- .message {
148
- padding: 1rem;
149
- border-radius: 1rem;
150
- border: 1px solid transparent;
151
- background: rgba(255, 255, 255, 0.8);
152
- box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.25);
153
- line-height: 1.6;
154
- }
155
-
156
- .message.assistant {
157
- background: linear-gradient(135deg, rgba(76, 110, 245, 0.1), rgba(168, 85, 247, 0.12));
158
- border-color: rgba(76, 110, 245, 0.25);
159
- }
160
-
161
- .message.user {
162
- align-self: flex-end;
163
- background: rgba(15, 23, 42, 0.9);
164
- color: #fff;
165
- border-color: rgba(255, 255, 255, 0.1);
166
- }
167
-
168
- .chat-form textarea {
169
- width: 100%;
170
- border-radius: 1rem;
171
- border: 1px solid var(--border);
172
- background: transparent;
173
- color: var(--text);
174
- font-size: 1rem;
175
- font-family: inherit;
176
- padding: 1rem;
177
- resize: none;
178
- }
179
-
180
- .form-actions {
181
- display: flex;
182
- align-items: center;
183
- justify-content: space-between;
184
- margin-top: 0.75rem;
185
- }
186
-
187
- .primary {
188
- border: none;
189
- border-radius: 999px;
190
- padding: 0.7rem 1.5rem;
191
- background: linear-gradient(135deg, var(--accent-start), var(--accent-end));
192
- color: #fff;
193
- font-weight: 600;
194
- cursor: pointer;
195
- }
196
-
197
- .hint {
198
- margin: 0;
199
- font-size: 0.82rem;
200
- color: var(--muted);
201
- }
202
-
203
- .agent-steps summary {
204
- font-weight: 600;
205
- font-size: 1rem;
206
- cursor: pointer;
207
- }
208
-
209
- .agent-steps ul {
210
- list-style: none;
211
- padding: 0;
212
- margin: 0.75rem 0 0;
213
- display: grid;
214
- gap: 0.5rem;
215
- }
216
-
217
- .agent-steps li {
218
- padding: 0.55rem 0.8rem;
219
- border-radius: 0.75rem;
220
- border: 1px solid var(--border);
221
- background: rgba(255, 255, 255, 0.5);
222
- font-size: 0.95rem;
223
- color: var(--text);
224
- }
225
-
226
- .agent-steps li.active {
227
- background: linear-gradient(135deg, rgba(76, 110, 245, 0.1), rgba(76, 110, 245, 0.25));
228
- border-color: rgba(76, 110, 245, 0.5);
229
- }
230
-
231
- .agent-steps li.done {
232
- opacity: 0.7;
233
- }
234
-
235
- .control-panel {
236
- display: flex;
237
- flex-direction: column;
238
- gap: 1.25rem;
239
- }
240
-
241
- .card header h2 {
242
- margin: 0;
243
- }
244
-
245
- .card header p {
246
- margin: 0.25rem 0 0;
247
- color: var(--muted);
248
- }
249
-
250
- .persona-list {
251
- margin: 1rem 0;
252
- display: grid;
253
- gap: 0.4rem;
254
- }
255
-
256
- .persona-row {
257
- padding: 0.75rem 1rem;
258
- border-radius: 1rem;
259
- border: 1px solid var(--border);
260
- background: var(--card-bg);
261
- }
262
-
263
- .persona-row.active {
264
- border-color: rgba(76, 110, 245, 0.8);
265
- box-shadow: inset 0 0 0 1px rgba(76, 110, 245, 0.15);
266
- }
267
-
268
- .persona-row strong {
269
- display: block;
270
- font-size: 1rem;
271
- }
272
-
273
- .persona-row .muted {
274
- font-size: 0.78rem;
275
- color: var(--muted);
276
- }
277
-
278
- .persona-card li {
279
- font-size: 0.9rem;
280
- }
281
-
282
- .stacked {
283
- display: flex;
284
- flex-direction: column;
285
- gap: 0.5rem;
286
- margin-top: 0.8rem;
287
- }
288
-
289
- textarea,
290
- input[type='text'],
291
- select {
292
- width: 100%;
293
- border-radius: 0.75rem;
294
- border: 1px solid var(--border);
295
- padding: 0.75rem 1rem;
296
- background: rgba(255, 255, 255, 0.6);
297
- color: var(--text);
298
- font-family: inherit;
299
- }
300
-
301
- .upload-field input[type='file'] {
302
- border: none;
303
- padding: 0.6rem 0;
304
- }
305
-
306
- .subtle {
307
- font-size: 0.85rem;
308
- color: var(--muted);
309
- }
310
-
311
- @media (max-width: 1080px) {
312
- .panel-grid {
313
- grid-template-columns: 1fr;
314
- }
315
-
316
- .control-panel {
317
- flex-direction: column;
318
- }
319
- }