@aion0/forge 0.5.16 → 0.5.18

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 CHANGED
@@ -2,7 +2,7 @@
2
2
  "mcpServers": {
3
3
  "forge": {
4
4
  "type": "sse",
5
- "url": "http://localhost:8406/sse?workspaceId=656c9e65-9d73-4cb6-a065-60d966e1fc78"
5
+ "url": "http://localhost:8406/sse?workspaceId=656c9e65-9d73-4cb6-a065-60d966e1fc78&agentId=engineer-1774920478256"
6
6
  }
7
7
  }
8
8
  }
package/RELEASE_NOTES.md CHANGED
@@ -1,12 +1,19 @@
1
- # Forge v0.5.16
1
+ # Forge v0.5.18
2
2
 
3
- Released: 2026-03-31
3
+ Released: 2026-04-01
4
4
 
5
- ## Changes since v0.5.15
5
+ ## Changes since v0.5.17
6
6
 
7
- ### Features
8
- - feat: code search in project sidebar
9
- - feat: git enhancements branch switch, resizable changes, more log
7
+ ### Bug Fixes
8
+ - Revert "fix: after hook done, suppress extends until file stops changing"
9
+ - fix: after hook done, suppress extends until file stops changing
10
+ - fix: warmup back to 7 polls (21s)
11
+ - fix: only start session monitor on new tmux creation, not existing
12
+ - fix: reduce warmup from 7 polls (21s) to 4 polls (12s)
13
+ - fix: resetState doesn't reset warmupCount, only startMonitoring does
10
14
 
15
+ ### Other
16
+ - Revert "fix: after hook done, suppress extends until file stops changing"
11
17
 
12
- **Full Changelog**: https://github.com/aiwatching/forge/compare/v0.5.15...v0.5.16
18
+
19
+ **Full Changelog**: https://github.com/aiwatching/forge/compare/v0.5.17...v0.5.18
@@ -1633,6 +1633,8 @@ export class WorkspaceOrchestrator extends EventEmitter {
1633
1633
  const entry = this.agents.get(agentId);
1634
1634
  if (!entry) return;
1635
1635
  entry.state.tmuxSession = undefined;
1636
+ // Reset session monitor warmup so it doesn't immediately trigger running on restart
1637
+ this.sessionMonitor?.resetState(agentId);
1636
1638
  this.saveNow();
1637
1639
  this.emitAgentsChanged();
1638
1640
  }
@@ -2151,10 +2153,17 @@ export class WorkspaceOrchestrator extends EventEmitter {
2151
2153
  if (supportsSession) {
2152
2154
  let sessionId: string | undefined;
2153
2155
  if (config.primary) {
2156
+ // Read fixedSessionId directly from file (avoid dynamic import issues in production build)
2154
2157
  try {
2155
- const { getFixedSession } = await import('../project-sessions') as any;
2156
- sessionId = getFixedSession(this.projectPath);
2157
- } catch {}
2158
+ const psPath = join(homedir(), '.forge', 'data', 'project-sessions.json');
2159
+ if (existsSync(psPath)) {
2160
+ const psData = JSON.parse(readFileSync(psPath, 'utf-8'));
2161
+ sessionId = psData[this.projectPath];
2162
+ }
2163
+ console.log(`[daemon] ${config.label}: fixedSession=${sessionId || 'NONE'} for ${this.projectPath}`);
2164
+ } catch (err: any) {
2165
+ console.error(`[daemon] ${config.label}: failed to read fixedSession: ${err.message}`);
2166
+ }
2158
2167
  } else {
2159
2168
  sessionId = config.boundSessionId;
2160
2169
  }
@@ -2255,6 +2264,11 @@ export class WorkspaceOrchestrator extends EventEmitter {
2255
2264
  this.emitAgentsChanged();
2256
2265
  }
2257
2266
 
2267
+ // Start session monitor only for newly created sessions (not existing ones)
2268
+ if (!sessionAlreadyExists) {
2269
+ this.startAgentSessionMonitor(agentId, config);
2270
+ }
2271
+
2258
2272
  // Ensure boundSessionId is set (required for session monitor + --resume)
2259
2273
  if (!config.primary && !config.boundSessionId) {
2260
2274
  const bindDelay = sessionAlreadyExists ? 500 : 5000;
@@ -47,6 +47,7 @@ export class SessionFileMonitor extends EventEmitter {
47
47
  this.stopMonitoring(agentId);
48
48
  this.currentState.set(agentId, 'idle');
49
49
  this.lastStableTime.set(agentId, Date.now());
50
+ this.warmupCount.set(agentId, 0); // reset warmup for fresh start
50
51
 
51
52
  const timer = setInterval(() => {
52
53
  this.checkFile(agentId, sessionFilePath);
@@ -96,7 +97,8 @@ export class SessionFileMonitor extends EventEmitter {
96
97
  resetState(agentId: string): void {
97
98
  this.currentState.set(agentId, 'idle');
98
99
  this.lastStableTime.set(agentId, Date.now());
99
- // Suppress state changes for 10s after manual reset
100
+ // Don't reset warmupCount here warmup only on startMonitoring (fresh start/restart)
101
+ // 10s suppress is enough to prevent immediate flip-back after hook/button
100
102
  this.suppressUntil.set(agentId, Date.now() + 10_000);
101
103
  }
102
104
 
@@ -110,20 +112,23 @@ export class SessionFileMonitor extends EventEmitter {
110
112
  return join(homedir(), '.claude', 'projects', encoded, `${sessionId}.jsonl`);
111
113
  }
112
114
 
113
- private initialized = new Set<string>();
115
+ private warmupCount = new Map<string, number>();
114
116
  private checkFile(agentId: string, filePath: string): void {
115
117
  try {
116
118
  const stat = statSync(filePath);
117
119
  const mtime = stat.mtimeMs;
118
120
  const size = stat.size;
119
121
 
120
- // First poll: just record baseline, don't trigger state change
121
- if (!this.initialized.has(agentId)) {
122
- this.initialized.add(agentId);
122
+ // Warmup: skip first 6 polls (~18s) to avoid false running during startup
123
+ // On poll 7 (first real check), just set new baseline — don't trigger running
124
+ const count = (this.warmupCount.get(agentId) || 0) + 1;
125
+ this.warmupCount.set(agentId, count);
126
+ if (count <= 7) {
123
127
  this.lastMtime.set(agentId, mtime);
124
128
  this.lastSize.set(agentId, size);
125
129
  this.lastStableTime.set(agentId, Date.now());
126
- console.log(`[session-monitor] ${agentId}: baseline mtime=${mtime} size=${size}`);
130
+ if (count === 1) console.log(`[session-monitor] ${agentId}: warmup started`);
131
+ if (count === 7) console.log(`[session-monitor] ${agentId}: warmup done, monitoring active`);
127
132
  return;
128
133
  }
129
134
 
@@ -163,8 +168,8 @@ export class SessionFileMonitor extends EventEmitter {
163
168
  }
164
169
  }
165
170
  } catch (err: any) {
166
- if (!this.initialized.has(`err-${agentId}`)) {
167
- this.initialized.add(`err-${agentId}`);
171
+ if ((this.warmupCount.get(`err-${agentId}`) || 0) === 0) {
172
+ this.warmupCount.set(`err-${agentId}`, 1);
168
173
  console.log(`[session-monitor] ${agentId}: checkFile error — ${err.message}`);
169
174
  }
170
175
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aion0/forge",
3
- "version": "0.5.16",
3
+ "version": "0.5.18",
4
4
  "description": "Unified AI workflow platform — multi-model task orchestration, persistent sessions, web terminal, remote access",
5
5
  "type": "module",
6
6
  "scripts": {