@aion0/forge 0.5.7 → 0.5.8
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 +128 -5
- 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 +545 -103
- 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 +740 -88
- 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/package.json +4 -2
|
@@ -310,11 +310,13 @@ const WebTerminal = forwardRef<WebTerminalHandle, WebTerminalProps>(function Web
|
|
|
310
310
|
// ─── Imperative handle for parent ─────────────────────
|
|
311
311
|
|
|
312
312
|
useImperativeHandle(ref, () => ({
|
|
313
|
-
openSessionInTerminal(sessionId: string, projectPath: string) {
|
|
313
|
+
async openSessionInTerminal(sessionId: string, projectPath: string) {
|
|
314
314
|
const tree = makeTerminal(undefined, projectPath);
|
|
315
315
|
const paneId = firstTerminalId(tree);
|
|
316
316
|
const sf = skipPermissions ? ' --dangerously-skip-permissions' : '';
|
|
317
|
-
|
|
317
|
+
let mcpFlag = '';
|
|
318
|
+
try { const { getMcpFlag } = await import('@/lib/session-utils'); mcpFlag = await getMcpFlag(projectPath); } catch {}
|
|
319
|
+
const cmd = `cd "${projectPath}" && claude --resume ${sessionId}${sf}${mcpFlag}\n`;
|
|
318
320
|
pendingCommands.set(paneId, cmd);
|
|
319
321
|
const projectName = projectPath.split('/').pop() || 'Terminal';
|
|
320
322
|
const newTab: TabState = {
|
|
@@ -334,12 +336,21 @@ const WebTerminal = forwardRef<WebTerminalHandle, WebTerminalProps>(function Web
|
|
|
334
336
|
const knownClis = ['claude', 'codex', 'aider'];
|
|
335
337
|
const agentCmd = knownClis.includes(agent) ? agent : 'claude';
|
|
336
338
|
|
|
337
|
-
// Resume flag
|
|
339
|
+
// Resume flag: explicit sessionId > fixedSession (set async below) > -c
|
|
338
340
|
let resumeFlag = '';
|
|
339
341
|
if (agentCmd === 'claude') {
|
|
340
342
|
if (sessionId) resumeFlag = ` --resume ${sessionId}`;
|
|
341
343
|
else if (resumeMode) resumeFlag = ' -c';
|
|
342
344
|
}
|
|
345
|
+
// Override with fixedSession if available (async, patched before command is sent)
|
|
346
|
+
let fixedSessionPending: Promise<void> | null = null;
|
|
347
|
+
if (agentCmd === 'claude' && !sessionId && projectPath) {
|
|
348
|
+
fixedSessionPending = import('@/lib/session-utils').then(({ resolveFixedSession }) =>
|
|
349
|
+
resolveFixedSession(projectPath).then(fixedId => {
|
|
350
|
+
if (fixedId) resumeFlag = ` --resume ${fixedId}`;
|
|
351
|
+
})
|
|
352
|
+
).catch(() => {});
|
|
353
|
+
}
|
|
343
354
|
|
|
344
355
|
// Model flag from profile
|
|
345
356
|
const modelFlag = profileEnv?.CLAUDE_MODEL ? ` --model ${profileEnv.CLAUDE_MODEL}` : '';
|
|
@@ -368,6 +379,15 @@ const WebTerminal = forwardRef<WebTerminalHandle, WebTerminalProps>(function Web
|
|
|
368
379
|
if (skipPermissions && agentCmd === 'claude') sf = ' --dangerously-skip-permissions';
|
|
369
380
|
}
|
|
370
381
|
|
|
382
|
+
// Wait for fixedSession resolution before building command
|
|
383
|
+
if (fixedSessionPending) await fixedSessionPending;
|
|
384
|
+
|
|
385
|
+
// MCP + env vars for claude-code agents
|
|
386
|
+
let mcpFlag = '';
|
|
387
|
+
if (agentCmd === 'claude' && projectPath) {
|
|
388
|
+
try { const { getMcpFlag } = await import('@/lib/session-utils'); mcpFlag = await getMcpFlag(projectPath); } catch {}
|
|
389
|
+
}
|
|
390
|
+
|
|
371
391
|
let targetTabId: number | null = null;
|
|
372
392
|
|
|
373
393
|
setTabs(prev => {
|
|
@@ -378,7 +398,7 @@ const WebTerminal = forwardRef<WebTerminalHandle, WebTerminalProps>(function Web
|
|
|
378
398
|
}
|
|
379
399
|
const tree = makeTerminal(undefined, projectPath);
|
|
380
400
|
const paneId = firstTerminalId(tree);
|
|
381
|
-
pendingCommands.set(paneId, `${envPrefix}cd "${projectPath}" && ${agentCmd}${resumeFlag}${modelFlag}${sf}\n`);
|
|
401
|
+
pendingCommands.set(paneId, `${envPrefix}cd "${projectPath}" && ${agentCmd}${resumeFlag}${modelFlag}${sf}${mcpFlag}\n`);
|
|
382
402
|
const newTab: TabState = {
|
|
383
403
|
id: nextId++,
|
|
384
404
|
label: projectName,
|
|
@@ -898,13 +918,13 @@ const WebTerminal = forwardRef<WebTerminalHandle, WebTerminalProps>(function Web
|
|
|
898
918
|
// Model flag (claude-code only)
|
|
899
919
|
const modelFlag = info.supportsSession && profileEnv.CLAUDE_MODEL ? ` --model ${profileEnv.CLAUDE_MODEL}` : '';
|
|
900
920
|
|
|
901
|
-
// Resume flag
|
|
921
|
+
// Resume flag: use fixedSession if available, else -c
|
|
902
922
|
let resumeFlag = '';
|
|
903
923
|
if (info.supportsSession) {
|
|
904
924
|
try {
|
|
905
|
-
const
|
|
906
|
-
const
|
|
907
|
-
|
|
925
|
+
const { resolveFixedSession, buildResumeFlag } = await import('@/lib/session-utils');
|
|
926
|
+
const fixedId = await resolveFixedSession(p.path);
|
|
927
|
+
resumeFlag = buildResumeFlag(fixedId, true);
|
|
908
928
|
} catch {}
|
|
909
929
|
}
|
|
910
930
|
|
|
@@ -917,7 +937,13 @@ const WebTerminal = forwardRef<WebTerminalHandle, WebTerminalProps>(function Web
|
|
|
917
937
|
sf = cfg?.skipPermissionsFlag ? ` ${cfg.skipPermissionsFlag}` : (cliCmd === 'claude' ? ' --dangerously-skip-permissions' : '');
|
|
918
938
|
}
|
|
919
939
|
|
|
920
|
-
|
|
940
|
+
// MCP + env vars
|
|
941
|
+
let mcpFlag = '';
|
|
942
|
+
if (cliCmd === 'claude') {
|
|
943
|
+
try { const { getMcpFlag } = await import('@/lib/session-utils'); mcpFlag = await getMcpFlag(p.path); } catch {}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
cmd = `${envExports}cd "${p.path}" && ${cliCmd}${resumeFlag}${modelFlag}${sf}${mcpFlag}\n`;
|
|
921
947
|
} catch {
|
|
922
948
|
cmd = `cd "${p.path}" && ${a.id}\n`;
|
|
923
949
|
}
|
|
@@ -1448,29 +1474,18 @@ const MemoTerminalPane = memo(function TerminalPane({
|
|
|
1448
1474
|
// Auto-run claude for project tabs (only if no pendingCommand already set)
|
|
1449
1475
|
if (isNewlyCreated && projectPathRef.current && !pendingCommands.has(id)) {
|
|
1450
1476
|
isNewlyCreated = false;
|
|
1451
|
-
// Check if project has existing claude sessions to decide -c flag
|
|
1452
1477
|
const pp = projectPathRef.current;
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
.then(sData => {
|
|
1457
|
-
const hasSession = Array.isArray(sData) ? sData.length > 0 : false;
|
|
1458
|
-
const resumeFlag = hasSession ? ' -c' : '';
|
|
1459
|
-
const skipFlag = skipPermRef.current ? ' --dangerously-skip-permissions' : '';
|
|
1460
|
-
setTimeout(() => {
|
|
1461
|
-
if (!disposed && ws?.readyState === WebSocket.OPEN) {
|
|
1462
|
-
ws.send(JSON.stringify({ type: 'input', data: `cd "${pp}" && claude${resumeFlag}${skipFlag}\n` }));
|
|
1463
|
-
}
|
|
1464
|
-
}, 300);
|
|
1465
|
-
})
|
|
1466
|
-
.catch(() => {
|
|
1478
|
+
import('@/lib/session-utils').then(({ resolveFixedSession, buildResumeFlag, getMcpFlag }) => {
|
|
1479
|
+
Promise.all([resolveFixedSession(pp), getMcpFlag(pp)]).then(([fixedId, mcpFlag]) => {
|
|
1480
|
+
const resumeFlag = buildResumeFlag(fixedId, true);
|
|
1467
1481
|
const skipFlag = skipPermRef.current ? ' --dangerously-skip-permissions' : '';
|
|
1468
1482
|
setTimeout(() => {
|
|
1469
1483
|
if (!disposed && ws?.readyState === WebSocket.OPEN) {
|
|
1470
|
-
ws.send(JSON.stringify({ type: 'input', data: `cd "${pp}" && claude${skipFlag}\n` }));
|
|
1484
|
+
ws.send(JSON.stringify({ type: 'input', data: `cd "${pp}" && claude${resumeFlag}${skipFlag}${mcpFlag}\n` }));
|
|
1471
1485
|
}
|
|
1472
1486
|
}, 300);
|
|
1473
1487
|
});
|
|
1488
|
+
});
|
|
1474
1489
|
}
|
|
1475
1490
|
isNewlyCreated = false;
|
|
1476
1491
|
// Force tmux to redraw by toggling size, then send reset
|