@aion0/forge 0.5.42 → 0.5.44

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.
Files changed (70) hide show
  1. package/RELEASE_NOTES.md +16 -4
  2. package/components/Dashboard.tsx +22 -1
  3. package/components/WorkspaceView.tsx +15 -2
  4. package/intellij-plugin/README.md +53 -0
  5. package/intellij-plugin/build.gradle.kts +60 -0
  6. package/intellij-plugin/gradle/gradle-daemon-jvm.properties +12 -0
  7. package/intellij-plugin/gradle.properties +9 -0
  8. package/intellij-plugin/publish.sh +78 -0
  9. package/intellij-plugin/settings.gradle.kts +7 -0
  10. package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/LoginAction.kt +49 -0
  11. package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/LogoutAction.kt +18 -0
  12. package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/OpenWebUIAction.kt +13 -0
  13. package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/SwitchConnectionAction.kt +26 -0
  14. package/intellij-plugin/src/main/kotlin/com/aion0/forge/api/ForgeClient.kt +115 -0
  15. package/intellij-plugin/src/main/kotlin/com/aion0/forge/auth/Auth.kt +31 -0
  16. package/intellij-plugin/src/main/kotlin/com/aion0/forge/connection/ConnectionManager.kt +95 -0
  17. package/intellij-plugin/src/main/kotlin/com/aion0/forge/settings/ForgeConfigurable.kt +81 -0
  18. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/DocsView.kt +99 -0
  19. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/ForgeStatusBarWidgetFactory.kt +94 -0
  20. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/ForgeToolWindowFactory.kt +27 -0
  21. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/ForgeTreeView.kt +176 -0
  22. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/Helpers.kt +48 -0
  23. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/PipelinesView.kt +226 -0
  24. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/TerminalsView.kt +309 -0
  25. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/TreeNodeData.kt +33 -0
  26. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/WorkspacesView.kt +166 -0
  27. package/intellij-plugin/src/main/resources/META-INF/plugin.xml +88 -0
  28. package/intellij-plugin/src/main/resources/icons/forge.svg +3 -0
  29. package/lib/agents/index.ts +1 -1
  30. package/lib/help-docs/00-overview.md +1 -0
  31. package/lib/help-docs/11-workspace.md +2 -1
  32. package/lib/help-docs/13-ide-plugins.md +90 -0
  33. package/lib/help-docs/CLAUDE.md +3 -0
  34. package/lib/workspace/orchestrator.ts +80 -7
  35. package/lib/workspace/persistence.ts +16 -0
  36. package/lib/workspace/types.ts +13 -0
  37. package/lib/workspace/watch-manager.ts +65 -24
  38. package/lib/workspace-standalone.ts +5 -9
  39. package/next-env.d.ts +1 -1
  40. package/package.json +1 -1
  41. package/vscode-extension/.vscodeignore +11 -0
  42. package/vscode-extension/README.md +48 -0
  43. package/vscode-extension/media/icon.png +0 -0
  44. package/vscode-extension/media/icon.svg +3 -0
  45. package/vscode-extension/package-lock.json +4046 -0
  46. package/vscode-extension/package.json +514 -0
  47. package/vscode-extension/publish.sh +49 -0
  48. package/vscode-extension/src/api/client.ts +217 -0
  49. package/vscode-extension/src/auth/auth.ts +32 -0
  50. package/vscode-extension/src/commands/auth.ts +44 -0
  51. package/vscode-extension/src/commands/connection.ts +113 -0
  52. package/vscode-extension/src/commands/docs.ts +40 -0
  53. package/vscode-extension/src/commands/pipeline.ts +103 -0
  54. package/vscode-extension/src/commands/server.ts +50 -0
  55. package/vscode-extension/src/commands/smith.ts +112 -0
  56. package/vscode-extension/src/commands/task.ts +43 -0
  57. package/vscode-extension/src/commands/terminal.ts +279 -0
  58. package/vscode-extension/src/commands/workspace.ts +138 -0
  59. package/vscode-extension/src/connection/manager.ts +80 -0
  60. package/vscode-extension/src/docs/fs-provider.ts +94 -0
  61. package/vscode-extension/src/docs/result-provider.ts +33 -0
  62. package/vscode-extension/src/docs/transport.ts +22 -0
  63. package/vscode-extension/src/extension.ts +314 -0
  64. package/vscode-extension/src/statusbar.ts +70 -0
  65. package/vscode-extension/src/terminal/pseudoterm.ts +123 -0
  66. package/vscode-extension/src/views/docs.ts +145 -0
  67. package/vscode-extension/src/views/pipelines.ts +222 -0
  68. package/vscode-extension/src/views/terminals.ts +91 -0
  69. package/vscode-extension/src/views/workspaces.ts +243 -0
  70. package/vscode-extension/tsconfig.json +16 -0
@@ -21,10 +21,18 @@ interface WatchSnapshot {
21
21
  gitHash?: string;
22
22
  commandOutput?: string;
23
23
  logLineCount?: number; // last known line count in agent's logs.jsonl
24
- agentStatus?: string; // last known taskStatus of monitored agent
24
+ /** Per-target last-seen taskStatusChangedAt. Watch fires when the target's
25
+ * current taskStatusChangedAt > the value recorded here. Updated only after
26
+ * a successful (non-defer) match-check. */
27
+ agentStatusLastSeen?: Record<string, number>;
25
28
  sessionFileSize?: number; // last known file size of session JSONL (bytes)
26
29
  }
27
30
 
31
+ /** Statuses that mean "still working — defer firing until it settles". */
32
+ function isBusyStatus(s: string | undefined): boolean {
33
+ return s === 'running' || s === 'starting';
34
+ }
35
+
28
36
  interface WatchChange {
29
37
  targetType: WatchTarget['type'];
30
38
  description: string;
@@ -330,7 +338,7 @@ export class WatchManager extends EventEmitter {
330
338
  constructor(
331
339
  private workspaceId: string,
332
340
  private projectPath: string,
333
- private getAgents: () => Map<string, { config: WorkspaceAgentConfig; state: { smithStatus: string; taskStatus: string; mode: string } }>,
341
+ private getAgents: () => Map<string, { config: WorkspaceAgentConfig; state: { smithStatus: string; taskStatus: string; mode?: string; taskStatusChangedAt?: number } }>,
334
342
  ) {
335
343
  super();
336
344
  }
@@ -346,9 +354,7 @@ export class WatchManager extends EventEmitter {
346
354
 
347
355
  /** Stop all watch loops */
348
356
  stop(): void {
349
- for (const [id, timer] of this.timers) {
350
- clearInterval(timer);
351
- }
357
+ for (const timer of this.timers.values()) clearInterval(timer);
352
358
  this.timers.clear();
353
359
  console.log(`[watch] All watch loops stopped`);
354
360
  }
@@ -392,7 +398,9 @@ export class WatchManager extends EventEmitter {
392
398
  const now = Date.now();
393
399
  const prev = this.snapshots.get(agentId) || { lastCheckTime: now };
394
400
  const allChanges: WatchChange[] = [];
395
- const newSnapshot: WatchSnapshot = { lastCheckTime: now };
401
+ const newSnapshot: WatchSnapshot = {
402
+ lastCheckTime: now,
403
+ };
396
404
 
397
405
  for (const target of config.watch!.targets) {
398
406
  switch (target.type) {
@@ -456,25 +464,58 @@ export class WatchManager extends EventEmitter {
456
464
  break;
457
465
  }
458
466
  case 'agent_status': {
459
- // Monitor another agent's task status (running done/failed)
460
- const targetAgentId = target.path; // path = agent ID to monitor
461
- if (targetAgentId) {
462
- const agents = this.getAgents();
463
- const targetEntry = agents.get(targetAgentId);
464
- if (targetEntry) {
465
- const currentStatus = targetEntry.state.taskStatus;
466
- const prevStatus = prev.agentStatus;
467
- newSnapshot.agentStatus = currentStatus;
468
- if (prevStatus && prevStatus !== currentStatus) {
469
- const label = targetEntry.config.label;
470
- // Match pattern if specified (e.g., "done" or "failed")
471
- const pattern = target.pattern;
472
- if (!pattern || currentStatus.match(new RegExp(pattern, 'i'))) {
473
- allChanges.push({ targetType: 'agent_status', description: `Agent ${label} status: ${prevStatus} → ${currentStatus}`, files: [] });
474
- }
475
- }
476
- }
467
+ // Event-driven detection via taskStatusChangedAt timestamp on the target
468
+ // agent's state (orchestrator updates it on every real transition).
469
+ // The watch tick just compares timestamps — no fast-tick polling needed.
470
+ const targetAgentId = target.path;
471
+ if (!targetAgentId) break;
472
+ const agents = this.getAgents();
473
+ const targetEntry = agents.get(targetAgentId);
474
+ if (!targetEntry) break;
475
+
476
+ const cur = targetEntry.state.taskStatus;
477
+ const curChangedAt = targetEntry.state.taskStatusChangedAt || 0;
478
+ const lastSeenMap = newSnapshot.agentStatusLastSeen || (newSnapshot.agentStatusLastSeen = { ...(prev.agentStatusLastSeen || {}) });
479
+
480
+ // Initial run: record current changedAt so we don't fire on the
481
+ // existing state. Future transitions will advance the timestamp.
482
+ if (initialRun) {
483
+ lastSeenMap[targetAgentId] = curChangedAt;
484
+ console.log(`[watch] ${config.label}: agent_status baseline — ${targetEntry.config.label}=${cur} @ ${curChangedAt}`);
485
+ break;
486
+ }
487
+
488
+ const prevSeen = lastSeenMap[targetAgentId] || 0;
489
+
490
+ // No transition since last check.
491
+ if (curChangedAt <= prevSeen) {
492
+ console.log(`[watch] ${config.label}: ${targetEntry.config.label} no transition since last check (curAt=${curChangedAt})`);
493
+ break;
494
+ }
495
+
496
+ // Target is mid-transition (still busy) — defer; do NOT update lastSeen
497
+ // so the next interval-tick can still see this as a pending transition.
498
+ if (isBusyStatus(cur)) {
499
+ console.log(`[watch] ${config.label}: ${targetEntry.config.label} = ${cur} (busy) → defer`);
500
+ break;
501
+ }
502
+
503
+ // Settled state. Match pattern against final state only.
504
+ const pattern = target.pattern;
505
+ const matched = pattern ? !!cur.match(new RegExp(pattern, 'i')) : true;
506
+ if (matched) {
507
+ console.log(`[watch] ${config.label}: ${targetEntry.config.label} → ${cur} (changed since ${prevSeen}) pattern='${pattern || '(any)'}' → FIRE`);
508
+ allChanges.push({
509
+ targetType: 'agent_status',
510
+ description: `Agent ${targetEntry.config.label} settled to ${cur}${pattern ? ` (matched '${pattern}')` : ''}`,
511
+ files: [],
512
+ });
513
+ } else {
514
+ console.log(`[watch] ${config.label}: ${targetEntry.config.label} → ${cur} pattern='${pattern}' → no match`);
477
515
  }
516
+ // Mark this transition as seen, so we don't fire again until the next
517
+ // real transition advances the timestamp.
518
+ lastSeenMap[targetAgentId] = curChangedAt;
478
519
  break;
479
520
  }
480
521
  }
@@ -312,10 +312,9 @@ async function handleAgentsPost(id: string, body: any, res: ServerResponse): Pro
312
312
  if (body.resolveOnly) {
313
313
  let currentSessionId: string | null = null;
314
314
  if (agentConfig.primary) {
315
- try {
316
- const { getFixedSession } = await import('./project-sessions.js');
317
- currentSessionId = getFixedSession(orch.projectPath) || null;
318
- } catch {}
315
+ // Auto-bind to project's latest existing session if no fixedSession yet,
316
+ // so the picker can offer "Current Session" on a fresh workspace.
317
+ currentSessionId = orch.resolvePrimaryFixedSession();
319
318
  } else {
320
319
  currentSessionId = agentConfig.boundSessionId || null;
321
320
  }
@@ -744,11 +743,8 @@ async function handleSmith(id: string, body: any, res: ServerResponse): Promise<
744
743
  // Get the primary agent's tmux session + project-level fixed session
745
744
  const primary = orch.getPrimaryAgent();
746
745
  if (!primary) return json(res, { ok: false, error: 'No primary agent configured' });
747
- let fixedSessionId: string | null = null;
748
- try {
749
- const { getFixedSession } = await import('./project-sessions.js');
750
- fixedSessionId = getFixedSession(orch.projectPath) || null;
751
- } catch {}
746
+ // Auto-bind to project's latest existing session if no fixedSession yet.
747
+ const fixedSessionId = orch.resolvePrimaryFixedSession();
752
748
  return json(res, {
753
749
  ok: true,
754
750
  agentId: primary.config.id,
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/types/routes.d.ts";
3
+ import "./.next/dev/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.42",
3
+ "version": "0.5.44",
4
4
  "description": "Unified AI workflow platform — multi-model task orchestration, persistent sessions, web terminal, remote access",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -0,0 +1,11 @@
1
+ .vscode/**
2
+ .vscode-test/**
3
+ src/**
4
+ .gitignore
5
+ .yarnrc
6
+ vsc-extension-quickstart.md
7
+ tsconfig.json
8
+ **/.eslintrc.json
9
+ **/*.map
10
+ **/*.ts
11
+ package-lock.json
@@ -0,0 +1,48 @@
1
+ # Forge VSCode Extension
2
+
3
+ Native VSCode integration for [Forge](https://github.com/aiwatching/forge).
4
+
5
+ ## What it does
6
+
7
+ - **Sidebar** — workspaces, smiths, active terminals, and background tasks at a glance
8
+ - **Native terminals** — attach to or create Forge tmux sessions in VSCode's terminal panel (uses VSCode's own Pseudoterminal API; sessions show up next to your regular terminals)
9
+ - **Send selection** — pipe selected code to a running Forge terminal
10
+ - **Notifications** — smith bell events surface as VSCode notifications
11
+ - **Status bar** — connection indicator + quick command access
12
+
13
+ ## Setup
14
+
15
+ 1. Install Forge CLI: `npm install -g @aion0/forge`
16
+ 2. Start the server: `forge server start`
17
+ 3. Install this extension (VSCode → "Install from VSIX..." or marketplace)
18
+ 4. Cmd/Ctrl+Shift+P → `Forge: Login` → enter your admin password
19
+
20
+ The token is stored in VSCode's SecretStorage and reused across sessions.
21
+
22
+ ## Settings
23
+
24
+ | Key | Default | Description |
25
+ |-----|---------|-------------|
26
+ | `forge.serverUrl` | `http://localhost:8403` | Forge HTTP server |
27
+ | `forge.terminalUrl` | `ws://localhost:8404` | Terminal WebSocket |
28
+ | `forge.autoStart` | `false` | Spawn `forge server start` on activation if unreachable |
29
+ | `forge.notifications.enabled` | `true` | Smith-bell → VSCode notification |
30
+ | `forge.refreshInterval` | `5` | Tree refresh interval (seconds) |
31
+
32
+ ## Local install
33
+
34
+ ```bash
35
+ cd vscode-extension
36
+ npm install
37
+ npm run package # produces forge-vscode.vsix
38
+ code --install-extension forge-vscode.vsix
39
+ ```
40
+
41
+ ## Develop
42
+
43
+ ```bash
44
+ cd vscode-extension
45
+ npm install
46
+ npm run watch # in one terminal
47
+ # In VSCode: F5 to launch Extension Development Host
48
+ ```
Binary file
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
2
+ <path d="M12 2L3 7v6c0 5 3.5 9.5 9 11 5.5-1.5 9-6 9-11V7l-9-5zm0 4.5l5 2.8v3.4l-5-2.8-5 2.8V9.3l5-2.8zm-5 6.4l5 2.8v5.6c-3.4-1.4-5-4.6-5-7.6v-.8zm10 .8c0 3-1.6 6.2-5 7.6v-5.6l5-2.8v.8z"/>
3
+ </svg>