@aion0/forge 0.5.7 → 0.5.9
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/.forge/mcp.json +8 -0
- package/RELEASE_NOTES.md +10 -6
- package/app/api/monitor/route.ts +12 -0
- package/app/api/project-sessions/route.ts +61 -0
- package/app/api/workspace/route.ts +1 -1
- package/check-forge-status.sh +9 -0
- package/components/MonitorPanel.tsx +15 -0
- package/components/ProjectDetail.tsx +99 -5
- package/components/SessionView.tsx +67 -19
- package/components/WebTerminal.tsx +40 -25
- package/components/WorkspaceView.tsx +599 -109
- package/lib/claude-sessions.ts +26 -28
- package/lib/forge-mcp-server.ts +389 -0
- package/lib/forge-skills/forge-inbox.md +13 -12
- package/lib/forge-skills/forge-send.md +13 -6
- package/lib/forge-skills/forge-status.md +12 -12
- package/lib/project-sessions.ts +48 -0
- package/lib/session-utils.ts +49 -0
- package/lib/workspace/__tests__/state-machine.test.ts +2 -2
- package/lib/workspace/agent-worker.ts +2 -5
- package/lib/workspace/backends/cli-backend.ts +3 -0
- package/lib/workspace/orchestrator.ts +774 -90
- package/lib/workspace/persistence.ts +0 -1
- package/lib/workspace/types.ts +10 -6
- package/lib/workspace/watch-manager.ts +17 -7
- package/lib/workspace-standalone.ts +83 -27
- package/next-env.d.ts +1 -1
- package/package.json +4 -2
- package/qa/.forge/mcp.json +8 -0
|
@@ -147,7 +147,6 @@ export function loadWorkspace(workspaceId: string): WorkspaceState | null {
|
|
|
147
147
|
if ('status' in agentState && !('smithStatus' in agentState)) {
|
|
148
148
|
const oldStatus = (agentState as any).status;
|
|
149
149
|
(agentState as any).smithStatus = 'down';
|
|
150
|
-
(agentState as any).mode = (agentState as any).runMode || 'auto';
|
|
151
150
|
(agentState as any).taskStatus = (oldStatus === 'running' || oldStatus === 'listening') ? 'idle' :
|
|
152
151
|
(oldStatus === 'interrupted') ? 'idle' :
|
|
153
152
|
(oldStatus === 'waiting_approval') ? 'idle' :
|
package/lib/workspace/types.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface WorkspaceAgentConfig {
|
|
|
12
12
|
icon: string;
|
|
13
13
|
// Node type: 'agent' (default) or 'input' (user-provided requirements)
|
|
14
14
|
type?: 'agent' | 'input';
|
|
15
|
+
// Primary agent: one per workspace, terminal-only, root dir, fixed session
|
|
16
|
+
primary?: boolean;
|
|
15
17
|
// Input node: append-only entries (latest is active, older are history)
|
|
16
18
|
content?: string; // legacy single content (migrated to entries)
|
|
17
19
|
entries?: InputEntry[]; // incremental input history
|
|
@@ -32,6 +34,12 @@ export interface WorkspaceAgentConfig {
|
|
|
32
34
|
steps: AgentStep[];
|
|
33
35
|
// Approval gate
|
|
34
36
|
requiresApproval?: boolean;
|
|
37
|
+
// Persistent terminal: keep a tmux+claude session alive, inject messages directly
|
|
38
|
+
persistentSession?: boolean;
|
|
39
|
+
// Bound CLI session ID for this agent (like fixedSessionId but per-agent)
|
|
40
|
+
boundSessionId?: string;
|
|
41
|
+
// Skip dangerous permissions check (default true when persistentSession is enabled)
|
|
42
|
+
skipPermissions?: boolean;
|
|
35
43
|
// Watch: autonomous periodic monitoring
|
|
36
44
|
watch?: WatchConfig;
|
|
37
45
|
}
|
|
@@ -79,16 +87,12 @@ export type SmithStatus = 'down' | 'active';
|
|
|
79
87
|
/** Task layer: current work execution */
|
|
80
88
|
export type TaskStatus = 'idle' | 'running' | 'done' | 'failed';
|
|
81
89
|
|
|
82
|
-
/** Agent execution mode */
|
|
83
|
-
export type AgentMode = 'auto' | 'manual';
|
|
84
|
-
|
|
85
90
|
/** @deprecated Use SmithStatus + TaskStatus instead */
|
|
86
91
|
export type AgentStatus = SmithStatus | TaskStatus | 'paused' | 'waiting_approval' | 'listening' | 'interrupted';
|
|
87
92
|
|
|
88
93
|
export interface AgentState {
|
|
89
94
|
// ─── Smith layer (daemon lifecycle) ─────
|
|
90
95
|
smithStatus: SmithStatus; // down=not started, active=listening on bus
|
|
91
|
-
mode: AgentMode; // auto=respond to messages, manual=user in terminal
|
|
92
96
|
|
|
93
97
|
// ─── Task layer (current work) ──────────
|
|
94
98
|
taskStatus: TaskStatus; // idle/running/done/failed
|
|
@@ -101,7 +105,7 @@ export interface AgentState {
|
|
|
101
105
|
lastCheckpoint?: number;
|
|
102
106
|
cliSessionId?: string;
|
|
103
107
|
currentMessageId?: string; // bus message that triggered current/last task execution
|
|
104
|
-
tmuxSession?: string; // tmux session name
|
|
108
|
+
tmuxSession?: string; // tmux session name (persistent or user-opened terminal)
|
|
105
109
|
startedAt?: number;
|
|
106
110
|
completedAt?: number;
|
|
107
111
|
error?: string;
|
|
@@ -224,7 +228,7 @@ export interface AgentBackend {
|
|
|
224
228
|
// ─── Worker Events ───────────────────────────────────────
|
|
225
229
|
|
|
226
230
|
export type WorkerEvent =
|
|
227
|
-
| { type: 'smith_status'; agentId: string; smithStatus: SmithStatus
|
|
231
|
+
| { type: 'smith_status'; agentId: string; smithStatus: SmithStatus }
|
|
228
232
|
| { type: 'task_status'; agentId: string; taskStatus: TaskStatus; error?: string }
|
|
229
233
|
| { type: 'log'; agentId: string; entry: TaskLogEntry }
|
|
230
234
|
| { type: 'step'; agentId: string; stepIndex: number; stepLabel: string }
|
|
@@ -206,8 +206,10 @@ function detectAgentLogChanges(workspaceId: string, targetAgentId: string, patte
|
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
// Track which session file was used last (to detect file switch)
|
|
210
|
+
const lastSessionFile = new Map<string, string>();
|
|
211
|
+
|
|
209
212
|
function detectSessionChanges(projectPath: string, pattern: string | undefined, prevLineCount: number, contextChars = 500, sessionId?: string): { changes: WatchChange | null; lineCount: number } {
|
|
210
|
-
// Find session file for this project
|
|
211
213
|
const claudeHome = join(homedir(), '.claude', 'projects');
|
|
212
214
|
const encoded = projectPath.replace(/\//g, '-');
|
|
213
215
|
const sessionDir = join(claudeHome, encoded);
|
|
@@ -217,11 +219,9 @@ function detectSessionChanges(projectPath: string, pattern: string | undefined,
|
|
|
217
219
|
let latestFile: string;
|
|
218
220
|
|
|
219
221
|
if (sessionId) {
|
|
220
|
-
// Use specific session ID
|
|
221
222
|
latestFile = join(sessionDir, `${sessionId}.jsonl`);
|
|
222
223
|
if (!existsSync(latestFile)) return { changes: null, lineCount: prevLineCount };
|
|
223
224
|
} else {
|
|
224
|
-
// Find most recently modified .jsonl file
|
|
225
225
|
const files = readdirSync(sessionDir)
|
|
226
226
|
.filter(f => f.endsWith('.jsonl'))
|
|
227
227
|
.map(f => ({ name: f, mtime: statSync(join(sessionDir, f)).mtimeMs }))
|
|
@@ -229,7 +229,17 @@ function detectSessionChanges(projectPath: string, pattern: string | undefined,
|
|
|
229
229
|
if (files.length === 0) return { changes: null, lineCount: prevLineCount };
|
|
230
230
|
latestFile = join(sessionDir, files[0].name);
|
|
231
231
|
}
|
|
232
|
-
//
|
|
232
|
+
// Detect if session file changed (user started new session) → reset tracking
|
|
233
|
+
const cacheKey = `${projectPath}:${sessionId || 'latest'}`;
|
|
234
|
+
const prevFile = lastSessionFile.get(cacheKey);
|
|
235
|
+
if (prevFile && prevFile !== latestFile) {
|
|
236
|
+
// Session file switched — reset prevLineCount to read from start of new file
|
|
237
|
+
prevLineCount = 0;
|
|
238
|
+
console.log(`[watch] Session file switched: ${prevFile.split('/').pop()} → ${latestFile.split('/').pop()}`);
|
|
239
|
+
}
|
|
240
|
+
lastSessionFile.set(cacheKey, latestFile);
|
|
241
|
+
|
|
242
|
+
// Only read new bytes since last check (efficient for large files)
|
|
233
243
|
const fd = openSync(latestFile, 'r');
|
|
234
244
|
const fileSize = fstatSync(fd).size;
|
|
235
245
|
if (fileSize <= prevLineCount) { closeSync(fd); return { changes: null, lineCount: fileSize }; }
|
|
@@ -256,17 +266,16 @@ function detectSessionChanges(projectPath: string, pattern: string | undefined,
|
|
|
256
266
|
}
|
|
257
267
|
}
|
|
258
268
|
|
|
269
|
+
// Only extract the LAST assistant/result text (not all entries)
|
|
259
270
|
const entries: string[] = [];
|
|
260
|
-
for (const line of newLines) {
|
|
271
|
+
for (const line of [...newLines].reverse()) {
|
|
261
272
|
try {
|
|
262
273
|
const parsed = JSON.parse(line);
|
|
263
|
-
// Extract text content from various session JSONL formats
|
|
264
274
|
let text = '';
|
|
265
275
|
if (parsed.type === 'assistant' && parsed.message?.content) {
|
|
266
276
|
for (const block of (Array.isArray(parsed.message.content) ? parsed.message.content : [parsed.message.content])) {
|
|
267
277
|
if (typeof block === 'string') text += block;
|
|
268
278
|
else if (block.type === 'text' && block.text) text += block.text;
|
|
269
|
-
else if (block.type === 'tool_use') text += `[tool: ${block.name}] `;
|
|
270
279
|
}
|
|
271
280
|
} else if (parsed.type === 'result' && parsed.result) {
|
|
272
281
|
text = typeof parsed.result === 'string' ? parsed.result : JSON.stringify(parsed.result);
|
|
@@ -290,6 +299,7 @@ function detectSessionChanges(projectPath: string, pattern: string | undefined,
|
|
|
290
299
|
text = text.slice(0, contextChars);
|
|
291
300
|
}
|
|
292
301
|
entries.push(text);
|
|
302
|
+
break; // only take the last matching entry (we're scanning in reverse)
|
|
293
303
|
} catch {}
|
|
294
304
|
}
|
|
295
305
|
|
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
|
|
15
15
|
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
|
|
16
16
|
import { readdirSync, statSync } from 'node:fs';
|
|
17
|
-
import { join } from 'node:path';
|
|
17
|
+
import { join, resolve } from 'node:path';
|
|
18
18
|
import { homedir } from 'node:os';
|
|
19
19
|
import { WorkspaceOrchestrator, type OrchestratorEvent } from './workspace/orchestrator';
|
|
20
|
-
import { loadWorkspace, saveWorkspace } from './workspace/persistence';
|
|
20
|
+
import { loadWorkspace, saveWorkspace, findWorkspaceByProject } from './workspace/persistence';
|
|
21
21
|
import { installForgeSkills, applyProfileToProject } from './workspace/skill-installer';
|
|
22
22
|
import {
|
|
23
23
|
loadMemory, formatMemoryForDisplay, getMemoryStats,
|
|
@@ -30,7 +30,7 @@ import { execSync } from 'node:child_process';
|
|
|
30
30
|
|
|
31
31
|
const PORT = Number(process.env.WORKSPACE_PORT) || 8405;
|
|
32
32
|
const FORGE_PORT = Number(process.env.PORT) || 8403;
|
|
33
|
-
const MAX_ACTIVE =
|
|
33
|
+
const MAX_ACTIVE = 5;
|
|
34
34
|
|
|
35
35
|
// ─── State ───────────────────────────────────────────────
|
|
36
36
|
|
|
@@ -48,14 +48,6 @@ function loadOrchestrator(id: string): WorkspaceOrchestrator {
|
|
|
48
48
|
const existing = orchestrators.get(id);
|
|
49
49
|
if (existing) return existing;
|
|
50
50
|
|
|
51
|
-
// Enforce max active limit
|
|
52
|
-
if (orchestrators.size >= MAX_ACTIVE) {
|
|
53
|
-
const evicted = evictIdleWorkspace();
|
|
54
|
-
if (!evicted) {
|
|
55
|
-
throw new Error(`Maximum ${MAX_ACTIVE} active workspaces. Stop agents in another workspace first.`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
51
|
const state = loadWorkspace(id);
|
|
60
52
|
if (!state) throw new Error('Workspace not found');
|
|
61
53
|
|
|
@@ -285,12 +277,13 @@ async function handleAgentsPost(id: string, body: any, res: ServerResponse): Pro
|
|
|
285
277
|
return json(res, { ok: true, ...launchInfo });
|
|
286
278
|
}
|
|
287
279
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
280
|
+
// Primary agent: always return its fixed session, no selection
|
|
281
|
+
if (agentConfig.primary && agentState.tmuxSession) {
|
|
282
|
+
return json(res, { ok: true, primary: true, tmuxSession: agentState.tmuxSession, fixedSession: true, ...launchInfo });
|
|
283
|
+
}
|
|
291
284
|
|
|
292
|
-
if (agentState.
|
|
293
|
-
return json(res, { ok: true,
|
|
285
|
+
if (agentState.tmuxSession) {
|
|
286
|
+
return json(res, { ok: true, alreadyOpen: true, tmuxSession: agentState.tmuxSession, ...launchInfo });
|
|
294
287
|
}
|
|
295
288
|
|
|
296
289
|
orch.setManualMode(agentId);
|
|
@@ -299,7 +292,8 @@ async function handleAgentsPost(id: string, body: any, res: ServerResponse): Pro
|
|
|
299
292
|
|
|
300
293
|
return json(res, {
|
|
301
294
|
ok: true,
|
|
302
|
-
|
|
295
|
+
primary: agentConfig.primary || undefined,
|
|
296
|
+
fixedSession: agentConfig.primary || undefined,
|
|
303
297
|
skillsInstalled: result.installed,
|
|
304
298
|
agentId,
|
|
305
299
|
label: agentConfig.label,
|
|
@@ -308,7 +302,12 @@ async function handleAgentsPost(id: string, body: any, res: ServerResponse): Pro
|
|
|
308
302
|
}
|
|
309
303
|
case 'close_terminal': {
|
|
310
304
|
if (!agentId) return jsonError(res, 'agentId required');
|
|
311
|
-
|
|
305
|
+
if (body.kill) {
|
|
306
|
+
// Kill: clear tmuxSession → message loop falls back to headless (claude -p)
|
|
307
|
+
orch.clearTmuxSession(agentId);
|
|
308
|
+
console.log(`[workspace] ${agentId}: terminal killed, falling back to headless`);
|
|
309
|
+
}
|
|
310
|
+
// Suspend: tmuxSession stays, agent can reattach later
|
|
312
311
|
return json(res, { ok: true });
|
|
313
312
|
}
|
|
314
313
|
case 'create_ticket': {
|
|
@@ -380,12 +379,20 @@ async function handleAgentsPost(id: string, body: any, res: ServerResponse): Pro
|
|
|
380
379
|
return json(res, { ok: true });
|
|
381
380
|
}
|
|
382
381
|
case 'delete_message': {
|
|
383
|
-
const { messageId } = body;
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
382
|
+
const { messageId, messageIds } = body;
|
|
383
|
+
const ids: string[] = messageIds || (messageId ? [messageId] : []);
|
|
384
|
+
if (ids.length === 0) return jsonError(res, 'messageId or messageIds required');
|
|
385
|
+
for (const id of ids) orch.getBus().deleteMessage(id);
|
|
386
|
+
// Push updated bus log to frontend
|
|
387
|
+
orch.emit('event', { type: 'bus_log_updated', log: orch.getBus().getLog() } as any);
|
|
388
|
+
return json(res, { ok: true, deleted: ids.length });
|
|
387
389
|
}
|
|
388
390
|
case 'start_daemon': {
|
|
391
|
+
// Check active daemon count before starting
|
|
392
|
+
const activeCount = Array.from(orchestrators.values()).filter(o => o.isDaemonActive()).length;
|
|
393
|
+
if (activeCount >= MAX_ACTIVE && !orch.isDaemonActive()) {
|
|
394
|
+
return jsonError(res, `Maximum ${MAX_ACTIVE} active daemons. Stop agents in another workspace first.`);
|
|
395
|
+
}
|
|
389
396
|
orch.startDaemon().catch(err => {
|
|
390
397
|
console.error('[workspace] startDaemon error:', err.message);
|
|
391
398
|
});
|
|
@@ -637,9 +644,12 @@ async function handleSmith(id: string, body: any, res: ServerResponse): Promise<
|
|
|
637
644
|
|
|
638
645
|
case 'sessions': {
|
|
639
646
|
// List recent claude sessions for resume picker
|
|
640
|
-
// Uses the
|
|
647
|
+
// Uses the agent's workDir (or project root) to find sessions
|
|
641
648
|
try {
|
|
642
|
-
const
|
|
649
|
+
const agentConfig = agentId ? orch.getSnapshot().agents.find(a => a.id === agentId) : null;
|
|
650
|
+
const agentWorkDir = agentConfig?.workDir && agentConfig.workDir !== './' && agentConfig.workDir !== '.'
|
|
651
|
+
? join(orch.projectPath, agentConfig.workDir) : orch.projectPath;
|
|
652
|
+
const encoded = resolve(agentWorkDir).replace(/\//g, '-');
|
|
643
653
|
const sessDir = join(homedir(), '.claude', 'projects', encoded);
|
|
644
654
|
const entries = readdirSync(sessDir);
|
|
645
655
|
const files = entries
|
|
@@ -661,15 +671,33 @@ async function handleSmith(id: string, body: any, res: ServerResponse): Promise<
|
|
|
661
671
|
const snapshot = orch.getSnapshot();
|
|
662
672
|
const states = orch.getAllAgentStates();
|
|
663
673
|
const agents = snapshot.agents.map(a => ({
|
|
664
|
-
id: a.id, label: a.label, icon: a.icon, type: a.type,
|
|
674
|
+
id: a.id, label: a.label, icon: a.icon, type: a.type, primary: a.primary || undefined,
|
|
665
675
|
smithStatus: states[a.id]?.smithStatus || 'down',
|
|
666
|
-
mode: states[a.id]?.mode || 'auto',
|
|
667
676
|
taskStatus: states[a.id]?.taskStatus || 'idle',
|
|
677
|
+
hasTmux: !!states[a.id]?.tmuxSession,
|
|
668
678
|
currentStep: states[a.id]?.currentStep,
|
|
669
679
|
}));
|
|
670
680
|
return json(res, { agents });
|
|
671
681
|
}
|
|
672
682
|
|
|
683
|
+
case 'primary_session': {
|
|
684
|
+
// Get the primary agent's tmux session + project-level fixed session
|
|
685
|
+
const primary = orch.getPrimaryAgent();
|
|
686
|
+
if (!primary) return json(res, { ok: false, error: 'No primary agent configured' });
|
|
687
|
+
let fixedSessionId: string | null = null;
|
|
688
|
+
try {
|
|
689
|
+
const { getFixedSession } = await import('./project-sessions.js');
|
|
690
|
+
fixedSessionId = getFixedSession(orch.projectPath) || null;
|
|
691
|
+
} catch {}
|
|
692
|
+
return json(res, {
|
|
693
|
+
ok: true,
|
|
694
|
+
agentId: primary.config.id,
|
|
695
|
+
label: primary.config.label,
|
|
696
|
+
tmuxSession: primary.state.tmuxSession || null,
|
|
697
|
+
fixedSessionId,
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
|
|
673
701
|
default:
|
|
674
702
|
return jsonError(res, `Unknown action: ${action}`);
|
|
675
703
|
}
|
|
@@ -716,6 +744,28 @@ const server = createServer(async (req, res) => {
|
|
|
716
744
|
});
|
|
717
745
|
}
|
|
718
746
|
|
|
747
|
+
// Resolve projectPath → workspaceId + agentId (walks up directories)
|
|
748
|
+
if (path === '/resolve' && method === 'GET') {
|
|
749
|
+
const projectPath = query.get('projectPath') || '';
|
|
750
|
+
if (!projectPath) return jsonError(res, 'projectPath required');
|
|
751
|
+
// Walk up directories to find workspace
|
|
752
|
+
let dir = projectPath;
|
|
753
|
+
while (dir && dir !== '/') {
|
|
754
|
+
const ws = findWorkspaceByProject(dir);
|
|
755
|
+
if (ws) {
|
|
756
|
+
const primary = ws.agents?.find((a: any) => a.primary);
|
|
757
|
+
return json(res, {
|
|
758
|
+
workspaceId: ws.id,
|
|
759
|
+
projectPath: ws.projectPath,
|
|
760
|
+
projectName: ws.projectName,
|
|
761
|
+
primaryAgentId: primary?.id || null,
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
dir = dir.replace(/\/[^/]+$/, '') || '/';
|
|
765
|
+
}
|
|
766
|
+
return json(res, { workspaceId: null });
|
|
767
|
+
}
|
|
768
|
+
|
|
719
769
|
// Active workspaces
|
|
720
770
|
if (path === '/workspaces/active' && method === 'GET') {
|
|
721
771
|
return json(res, {
|
|
@@ -809,6 +859,12 @@ process.on('unhandledRejection', (err) => {
|
|
|
809
859
|
|
|
810
860
|
// ─── Start ───────────────────────────────────────────────
|
|
811
861
|
|
|
862
|
+
// Start MCP Server alongside workspace daemon
|
|
863
|
+
import { startMcpServer, setOrchestratorResolver, getMcpPort } from './forge-mcp-server.js';
|
|
864
|
+
setOrchestratorResolver((id: string) => loadOrchestrator(id));
|
|
865
|
+
const MCP_PORT = getMcpPort();
|
|
866
|
+
startMcpServer(MCP_PORT).catch(err => console.error('[forge-mcp] Failed to start:', err));
|
|
867
|
+
|
|
812
868
|
server.listen(PORT, () => {
|
|
813
|
-
console.log(`[workspace] Daemon started on http://0.0.0.0:${PORT} (max ${MAX_ACTIVE} workspaces)`);
|
|
869
|
+
console.log(`[workspace] Daemon started on http://0.0.0.0:${PORT} (max ${MAX_ACTIVE} workspaces, MCP on ${MCP_PORT})`);
|
|
814
870
|
});
|
package/next-env.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="next" />
|
|
2
2
|
/// <reference types="next/image-types/global" />
|
|
3
|
-
import "./.next/
|
|
3
|
+
import "./.next/types/routes.d.ts";
|
|
4
4
|
|
|
5
5
|
// NOTE: This file should not be edited
|
|
6
6
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aion0/forge",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.9",
|
|
4
4
|
"description": "Unified AI workflow platform — multi-model task orchestration, persistent sessions, web terminal, remote access",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"@ai-sdk/google": "^3.0.43",
|
|
33
33
|
"@ai-sdk/openai": "^3.0.41",
|
|
34
34
|
"@auth/core": "^0.34.3",
|
|
35
|
+
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
35
36
|
"@xterm/addon-fit": "^0.11.0",
|
|
36
37
|
"@xterm/addon-search": "^0.16.0",
|
|
37
38
|
"@xterm/addon-unicode11": "^0.9.0",
|
|
@@ -48,7 +49,8 @@
|
|
|
48
49
|
"react-markdown": "^10.1.0",
|
|
49
50
|
"remark-gfm": "^4.0.1",
|
|
50
51
|
"ws": "^8.19.0",
|
|
51
|
-
"yaml": "^2.8.2"
|
|
52
|
+
"yaml": "^2.8.2",
|
|
53
|
+
"zod": "^4.3.6"
|
|
52
54
|
},
|
|
53
55
|
"pnpm": {
|
|
54
56
|
"onlyBuiltDependencies": [
|