project-graph-mcp 2.3.0 → 2.3.2
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/package.json +1 -3
- package/project-graph-mcp-2.3.0.tgz +0 -0
- package/src/network/web-server.js +1 -1
- package/vendor/symbiote-node/engine/AgentUICommands.js +100 -0
- package/vendor/symbiote-node/engine/Executor.js +371 -0
- package/vendor/symbiote-node/engine/Graph.js +314 -0
- package/vendor/symbiote-node/engine/GraphServer.js +353 -0
- package/vendor/symbiote-node/engine/HandlerLoader.js +145 -0
- package/vendor/symbiote-node/engine/History.js +83 -0
- package/vendor/symbiote-node/engine/Lifecycle.js +118 -0
- package/vendor/symbiote-node/engine/Persistence.js +84 -0
- package/vendor/symbiote-node/engine/Registry.js +264 -0
- package/vendor/symbiote-node/engine/SocketTypes.js +79 -0
- package/vendor/symbiote-node/engine/cli.js +404 -0
- package/vendor/symbiote-node/engine/index.js +56 -0
- package/vendor/symbiote-node/engine/nanoid.js +28 -0
- package/vendor/symbiote-node/engine/package.json +26 -0
- package/vendor/symbiote-node/engine/packs/ai/beat-detect.handler.js +215 -0
- package/vendor/symbiote-node/engine/packs/ai/content-adapt.handler.js +238 -0
- package/vendor/symbiote-node/engine/packs/ai/face-detect.handler.js +287 -0
- package/vendor/symbiote-node/engine/packs/ai/grok-generate.handler.js +565 -0
- package/vendor/symbiote-node/engine/packs/ai/kling-lipsync.handler.js +414 -0
- package/vendor/symbiote-node/engine/packs/ai/lesson-generate.handler.js +343 -0
- package/vendor/symbiote-node/engine/packs/ai/opencode.handler.js +164 -0
- package/vendor/symbiote-node/engine/packs/ai/replicate-lipsync.handler.js +341 -0
- package/vendor/symbiote-node/engine/packs/ai/tts.handler.js +241 -0
- package/vendor/symbiote-node/engine/packs/ai/whisper.handler.js +191 -0
- package/vendor/symbiote-node/engine/packs/data/db-query.handler.js +67 -0
- package/vendor/symbiote-node/engine/packs/data/news-accumulate.handler.js +281 -0
- package/vendor/symbiote-node/engine/packs/data/personas.handler.js +160 -0
- package/vendor/symbiote-node/engine/packs/data/prompt-loader.handler.js +193 -0
- package/vendor/symbiote-node/engine/packs/data/roles.handler.js +216 -0
- package/vendor/symbiote-node/engine/packs/data/rss-feed.handler.js +244 -0
- package/vendor/symbiote-node/engine/packs/debug/inject.handler.js +52 -0
- package/vendor/symbiote-node/engine/packs/flow/agent.handler.js +73 -0
- package/vendor/symbiote-node/engine/packs/flow/if.handler.js +107 -0
- package/vendor/symbiote-node/engine/packs/flow/loop.handler.js +58 -0
- package/vendor/symbiote-node/engine/packs/flow/merge.handler.js +60 -0
- package/vendor/symbiote-node/engine/packs/flow/retry.handler.js +65 -0
- package/vendor/symbiote-node/engine/packs/flow/switch.handler.js +64 -0
- package/vendor/symbiote-node/engine/packs/flow/wait-all.handler.js +39 -0
- package/vendor/symbiote-node/engine/packs/io/http-request.handler.js +82 -0
- package/vendor/symbiote-node/engine/packs/io/read-file.handler.js +60 -0
- package/vendor/symbiote-node/engine/packs/io/write-file.handler.js +63 -0
- package/vendor/symbiote-node/engine/packs/transform/anchor-match.handler.js +494 -0
- package/vendor/symbiote-node/engine/packs/transform/effects-skeleton.handler.js +417 -0
- package/vendor/symbiote-node/engine/packs/transform/json-parse.handler.js +43 -0
- package/vendor/symbiote-node/engine/packs/transform/lipsync-select.handler.js +339 -0
- package/vendor/symbiote-node/engine/packs/transform/riopla-adapt.handler.js +432 -0
- package/vendor/symbiote-node/engine/packs/transform/set.handler.js +57 -0
- package/vendor/symbiote-node/engine/packs/transform/template-builder.handler.js +134 -0
- package/vendor/symbiote-node/engine/packs/transform/template.handler.js +79 -0
- package/vendor/symbiote-node/engine/packs/transform/timeline-build.handler.js +399 -0
- package/vendor/symbiote-node/engine/packs/util/delay.handler.js +39 -0
- package/vendor/symbiote-node/engine/packs/util/log.handler.js +44 -0
- package/vendor/symbiote-node/engine/packs/video-pack.js +323 -0
- package/vendor/symbiote-node/package.json +2 -2
- package/web/app.js +6 -3
- package/web/components/canvas-graph.js +50 -11
- package/web/components/code-block.js +1 -1
- package/web/components/event-feed/MiniGraphWidget.js +105 -15
- package/web/components/follow-ribbon.js +134 -0
- package/web/follow-controller.js +241 -0
- package/web/panels/code-viewer.js +1 -1
- package/web/panels/dep-graph.js +21 -42
- package/web/style.css +6 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// @ctx .context/web/components/follow-ribbon.ctx
|
|
2
|
+
/**
|
|
3
|
+
* FollowRibbon — Floating status bar that shows current agent action.
|
|
4
|
+
* Appears at the bottom of the screen during Follow Mode.
|
|
5
|
+
* Auto-fades after 4 seconds of inactivity.
|
|
6
|
+
*/
|
|
7
|
+
import Symbiote from '@symbiotejs/symbiote';
|
|
8
|
+
import { events } from '../app.js';
|
|
9
|
+
|
|
10
|
+
export class FollowRibbon extends Symbiote {
|
|
11
|
+
init$ = {
|
|
12
|
+
statusText: '',
|
|
13
|
+
visible: false,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
_fadeTimer = null;
|
|
17
|
+
|
|
18
|
+
initCallback() {
|
|
19
|
+
// Event subscriptions are in renderCallback (after template mount)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
renderCallback() {
|
|
23
|
+
this.sub('visible', (v) => {
|
|
24
|
+
this.toggleAttribute('visible', v);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
events.addEventListener('follow-status-changed', (e) => {
|
|
28
|
+
const text = e.detail?.text || '';
|
|
29
|
+
if (!text) {
|
|
30
|
+
this.$.visible = false;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
this.$.statusText = text;
|
|
34
|
+
this.$.visible = true;
|
|
35
|
+
|
|
36
|
+
// Auto-fade after 4 seconds
|
|
37
|
+
if (this._fadeTimer) clearTimeout(this._fadeTimer);
|
|
38
|
+
this._fadeTimer = setTimeout(() => {
|
|
39
|
+
this.$.visible = false;
|
|
40
|
+
}, 4000);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
events.addEventListener('follow-state-changed', (e) => {
|
|
44
|
+
if (!e.detail?.enabled) {
|
|
45
|
+
this.$.visible = false;
|
|
46
|
+
this.$.statusText = '';
|
|
47
|
+
if (this._fadeTimer) {
|
|
48
|
+
clearTimeout(this._fadeTimer);
|
|
49
|
+
this._fadeTimer = null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
FollowRibbon.template = `
|
|
57
|
+
<div class="fr-inner">
|
|
58
|
+
<span class="fr-icon">smart_toy</span>
|
|
59
|
+
<span class="fr-text" bind="textContent: statusText"></span>
|
|
60
|
+
<span class="fr-dots"></span>
|
|
61
|
+
</div>
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
FollowRibbon.rootStyles = `
|
|
65
|
+
follow-ribbon {
|
|
66
|
+
position: fixed;
|
|
67
|
+
bottom: 20px;
|
|
68
|
+
left: 50%;
|
|
69
|
+
transform: translateX(-50%) translateY(20px);
|
|
70
|
+
z-index: 9999;
|
|
71
|
+
pointer-events: none;
|
|
72
|
+
opacity: 0;
|
|
73
|
+
transition: opacity 0.4s ease, transform 0.4s ease;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
follow-ribbon[visible] {
|
|
77
|
+
opacity: 1;
|
|
78
|
+
transform: translateX(-50%) translateY(0);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.fr-inner {
|
|
82
|
+
display: flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
gap: 10px;
|
|
85
|
+
padding: 8px 20px;
|
|
86
|
+
border-radius: 24px;
|
|
87
|
+
background: rgba(20, 20, 25, 0.85);
|
|
88
|
+
backdrop-filter: blur(16px);
|
|
89
|
+
-webkit-backdrop-filter: blur(16px);
|
|
90
|
+
border: 1px solid rgba(76, 139, 245, 0.25);
|
|
91
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4), 0 0 16px rgba(76, 139, 245, 0.1);
|
|
92
|
+
font-family: 'Inter', -apple-system, sans-serif;
|
|
93
|
+
font-size: 12px;
|
|
94
|
+
font-weight: 500;
|
|
95
|
+
color: rgba(255, 255, 255, 0.9);
|
|
96
|
+
white-space: nowrap;
|
|
97
|
+
max-width: 500px;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.fr-icon {
|
|
101
|
+
font-family: 'Material Symbols Outlined';
|
|
102
|
+
font-size: 16px;
|
|
103
|
+
color: #4c8bf5;
|
|
104
|
+
animation: fr-pulse 2s ease-in-out infinite;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.fr-text {
|
|
108
|
+
overflow: hidden;
|
|
109
|
+
text-overflow: ellipsis;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.fr-dots::after {
|
|
113
|
+
content: '...';
|
|
114
|
+
animation: fr-dots 1.5s steps(3) infinite;
|
|
115
|
+
display: inline-block;
|
|
116
|
+
width: 16px;
|
|
117
|
+
text-align: left;
|
|
118
|
+
color: rgba(255, 255, 255, 0.4);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@keyframes fr-pulse {
|
|
122
|
+
0%, 100% { opacity: 1; }
|
|
123
|
+
50% { opacity: 0.5; }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@keyframes fr-dots {
|
|
127
|
+
0% { content: ''; }
|
|
128
|
+
33% { content: '.'; }
|
|
129
|
+
66% { content: '..'; }
|
|
130
|
+
100% { content: '...'; }
|
|
131
|
+
}
|
|
132
|
+
`;
|
|
133
|
+
|
|
134
|
+
FollowRibbon.reg('follow-ribbon');
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// @ctx .context/web/follow-controller.ctx
|
|
2
|
+
/**
|
|
3
|
+
* FollowController — Central orchestrator for Follow Mode.
|
|
4
|
+
*
|
|
5
|
+
* Classifies incoming tool-events and dispatches debounced focus-change
|
|
6
|
+
* signals to subscribed panels (graph, code-viewer, monitor).
|
|
7
|
+
* Also manages the status ribbon text shown during active follow.
|
|
8
|
+
*
|
|
9
|
+
* NOTE: Does NOT import from app.js to avoid circular dependency.
|
|
10
|
+
* Call init(events, emit) before enable().
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** Debounce delay for heavy visual updates (camera, code loading) */
|
|
14
|
+
const HEAVY_DEBOUNCE = 800;
|
|
15
|
+
|
|
16
|
+
class FollowController {
|
|
17
|
+
/** @type {boolean} */
|
|
18
|
+
enabled = false;
|
|
19
|
+
/** @type {{type: string, target: any, action?: string, meta?: object}|null} */
|
|
20
|
+
currentFocus = null;
|
|
21
|
+
/** @type {string} */
|
|
22
|
+
statusText = '';
|
|
23
|
+
/** @type {number|null} */
|
|
24
|
+
_debounceTimer = null;
|
|
25
|
+
/** @type {string|null} Previous hash before entering follow mode */
|
|
26
|
+
_previousHash = null;
|
|
27
|
+
/** @type {Function|null} */
|
|
28
|
+
_boundHandler = null;
|
|
29
|
+
/** @type {EventTarget|null} */
|
|
30
|
+
_events = null;
|
|
31
|
+
/** @type {Function|null} */
|
|
32
|
+
_emit = null;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Late-bind events bus and emit function (breaks circular import).
|
|
36
|
+
* Must be called once before enable().
|
|
37
|
+
* @param {EventTarget} events
|
|
38
|
+
* @param {Function} emit
|
|
39
|
+
*/
|
|
40
|
+
init(events, emit) {
|
|
41
|
+
this._events = events;
|
|
42
|
+
this._emit = emit;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
enable() {
|
|
46
|
+
if (this.enabled) return;
|
|
47
|
+
this.enabled = true;
|
|
48
|
+
|
|
49
|
+
// Save current location for restoring later
|
|
50
|
+
this._previousHash = location.hash;
|
|
51
|
+
|
|
52
|
+
// Bind tool-event listener
|
|
53
|
+
this._boundHandler = (e) => this._onToolEvent(e.detail);
|
|
54
|
+
this._events.addEventListener('tool-event', this._boundHandler);
|
|
55
|
+
|
|
56
|
+
this._emit('follow-state-changed', { enabled: true });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
disable() {
|
|
60
|
+
if (!this.enabled) return;
|
|
61
|
+
this.enabled = false;
|
|
62
|
+
|
|
63
|
+
// Clean up
|
|
64
|
+
if (this._boundHandler) {
|
|
65
|
+
this._events.removeEventListener('tool-event', this._boundHandler);
|
|
66
|
+
this._boundHandler = null;
|
|
67
|
+
}
|
|
68
|
+
if (this._debounceTimer) {
|
|
69
|
+
clearTimeout(this._debounceTimer);
|
|
70
|
+
this._debounceTimer = null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.currentFocus = null;
|
|
74
|
+
this._emitStatus('');
|
|
75
|
+
|
|
76
|
+
this._emit('follow-state-changed', { enabled: false });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** @returns {string|null} */
|
|
80
|
+
getPreviousHash() {
|
|
81
|
+
return this._previousHash;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Main tool-event dispatcher. Classifies the event and routes to appropriate action.
|
|
86
|
+
* @param {object} event - Tool event from WebSocket
|
|
87
|
+
*/
|
|
88
|
+
_onToolEvent(event) {
|
|
89
|
+
if (!this.enabled) return;
|
|
90
|
+
|
|
91
|
+
const toolName = event.tool || event.name || '';
|
|
92
|
+
const args = event.args || {};
|
|
93
|
+
const isCall = event.type === 'tool_call';
|
|
94
|
+
const isResult = event.type === 'tool_result';
|
|
95
|
+
|
|
96
|
+
// Extract short tool name (strip prefixes like 'default_api:', 'mcp_project-graph_')
|
|
97
|
+
const shortName = this._shortName(toolName);
|
|
98
|
+
|
|
99
|
+
// Status ribbon — update immediately on call
|
|
100
|
+
if (isCall) {
|
|
101
|
+
const statusText = this._buildStatusText(shortName, args);
|
|
102
|
+
if (statusText) this._emitStatus(statusText);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Visual focus — classify and dispatch (debounced for heavy ops)
|
|
106
|
+
const action = this._classify(shortName, args, isCall, isResult, event);
|
|
107
|
+
if (action) {
|
|
108
|
+
if (action.immediate) {
|
|
109
|
+
this._emitFocusNow(action.focus);
|
|
110
|
+
} else {
|
|
111
|
+
this._emitFocusDebounced(action.focus, action.debounce || HEAVY_DEBOUNCE);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Classify tool event into a visual action.
|
|
118
|
+
* Only handles tools emitted by our MCP server (navigate, get_skeleton, etc.).
|
|
119
|
+
* IDE-local tools (view_file, grep_search) never arrive over WebSocket.
|
|
120
|
+
* @returns {{focus: object, debounce?: number, immediate?: boolean}|null}
|
|
121
|
+
*/
|
|
122
|
+
_classify(tool, args, isCall, isResult, raw) {
|
|
123
|
+
if (!isCall) return null;
|
|
124
|
+
|
|
125
|
+
// === Graph navigation ===
|
|
126
|
+
if (tool === 'navigate') {
|
|
127
|
+
if (args.action === 'expand' && args.symbol) {
|
|
128
|
+
return { focus: { type: 'graph', target: args.symbol, action: 'focus' }, debounce: HEAVY_DEBOUNCE };
|
|
129
|
+
}
|
|
130
|
+
if (args.action === 'deps' && args.symbol) {
|
|
131
|
+
return { focus: { type: 'graph', target: args.symbol, action: 'deps' }, debounce: HEAVY_DEBOUNCE };
|
|
132
|
+
}
|
|
133
|
+
if (args.action === 'usages' && args.symbol) {
|
|
134
|
+
return { focus: { type: 'graph', target: args.symbol, action: 'deps' }, debounce: HEAVY_DEBOUNCE };
|
|
135
|
+
}
|
|
136
|
+
if (args.action === 'call_chain' && args.from && args.to) {
|
|
137
|
+
return { focus: { type: 'graph', target: { from: args.from, to: args.to }, action: 'chain' }, immediate: true };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// === Skeleton / Overview ===
|
|
142
|
+
if (tool === 'get_skeleton' || tool === 'get_ai_context') {
|
|
143
|
+
return { focus: { type: 'graph', action: 'fit' }, immediate: true };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// === Code compaction (compact_file action has a file path) ===
|
|
147
|
+
if (tool === 'compact' && args.path) {
|
|
148
|
+
return { focus: { type: 'file', target: args.path }, debounce: HEAVY_DEBOUNCE };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// === Analysis ===
|
|
152
|
+
if (tool === 'analyze') {
|
|
153
|
+
return { focus: { type: 'analysis' }, immediate: true };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Build human-readable status text for the ribbon.
|
|
161
|
+
* Only MCP-server tools arrive here (navigate, get_skeleton, compact, analyze, docs, etc.).
|
|
162
|
+
* @param {string} tool
|
|
163
|
+
* @param {object} args
|
|
164
|
+
* @returns {string}
|
|
165
|
+
*/
|
|
166
|
+
_buildStatusText(tool, args) {
|
|
167
|
+
const file = args.path || '';
|
|
168
|
+
const short = file ? file.split('/').slice(-2).join('/') : '';
|
|
169
|
+
|
|
170
|
+
switch (tool) {
|
|
171
|
+
case 'navigate': {
|
|
172
|
+
if (args.action === 'expand') return `Expanding ${args.symbol}`;
|
|
173
|
+
if (args.action === 'deps') return `Tracing deps of ${args.symbol}`;
|
|
174
|
+
if (args.action === 'usages') return `Finding usages of ${args.symbol}`;
|
|
175
|
+
if (args.action === 'call_chain') return `Tracing ${args.from} → ${args.to}`;
|
|
176
|
+
if (args.action === 'sub_projects') return `Scanning sub-projects`;
|
|
177
|
+
return `Navigating graph`;
|
|
178
|
+
}
|
|
179
|
+
case 'get_skeleton': return `Scanning project structure`;
|
|
180
|
+
case 'get_ai_context': return `Loading AI context`;
|
|
181
|
+
case 'compact': return `Compacting ${short}`;
|
|
182
|
+
case 'analyze': return `Analyzing: ${args.action || ''}`;
|
|
183
|
+
case 'docs': return `Documentation: ${args.action || ''}`;
|
|
184
|
+
case 'jsdoc': return `JSDoc: ${args.action || ''}`;
|
|
185
|
+
case 'db': return `Database: ${args.action || ''}`;
|
|
186
|
+
case 'testing': return `Tests: ${args.action || ''}`;
|
|
187
|
+
case 'filters': return `Filters: ${args.action || ''}`;
|
|
188
|
+
default: return tool ? `Running ${tool}` : '';
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Extract short tool name from full prefixed name.
|
|
194
|
+
* 'default_api:view_file' → 'view_file'
|
|
195
|
+
* 'mcp_project-graph_navigate' → 'navigate'
|
|
196
|
+
*/
|
|
197
|
+
_shortName(full) {
|
|
198
|
+
// Strip 'default_api:' prefix
|
|
199
|
+
let name = full.replace(/^default_api:/, '');
|
|
200
|
+
// Strip 'mcp_project-graph_' prefix
|
|
201
|
+
name = name.replace(/^mcp_project-graph_/, '');
|
|
202
|
+
return name;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Emit focus change immediately (for urgent actions like call_chain).
|
|
207
|
+
*/
|
|
208
|
+
_emitFocusNow(focus) {
|
|
209
|
+
if (this._debounceTimer) {
|
|
210
|
+
clearTimeout(this._debounceTimer);
|
|
211
|
+
this._debounceTimer = null;
|
|
212
|
+
}
|
|
213
|
+
this.currentFocus = focus;
|
|
214
|
+
this._emit('follow-focus-changed', focus);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Emit focus change with debounce (for rapid file reads, etc.).
|
|
219
|
+
*/
|
|
220
|
+
_emitFocusDebounced(focus, delay) {
|
|
221
|
+
if (this._debounceTimer) {
|
|
222
|
+
clearTimeout(this._debounceTimer);
|
|
223
|
+
}
|
|
224
|
+
this._debounceTimer = setTimeout(() => {
|
|
225
|
+
this._debounceTimer = null;
|
|
226
|
+
this.currentFocus = focus;
|
|
227
|
+
this._emit('follow-focus-changed', focus);
|
|
228
|
+
}, delay);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Emit status text for the ribbon.
|
|
233
|
+
*/
|
|
234
|
+
_emitStatus(text) {
|
|
235
|
+
this.statusText = text;
|
|
236
|
+
this._emit('follow-status-changed', { text });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Singleton instance */
|
|
241
|
+
export const followController = new FollowController();
|
|
@@ -29,7 +29,7 @@ export class CodeViewer extends e{init$={filename:"Select a file",hasFile:!1,vie
|
|
|
29
29
|
// Toggle between source and the transformation
|
|
30
30
|
this.$.viewMode=this.$.viewMode==="source"?"transformed":"source";
|
|
31
31
|
this._showCurrentMode();
|
|
32
|
-
}};_fileData=null;_isReadable=!1;_transformCache=null;_loadingTransform=!1;_currentPath=null;initCallback(){t.addEventListener("file-selected",e=>this._loadFile(e.detail.path));if(o.activeFile)requestAnimationFrame(()=>this._loadFile(o.activeFile))}renderCallback(){this.sub("hasFile",e=>{this.toggleAttribute("has-file",e)}),this.sub("viewMode",e=>{
|
|
32
|
+
}};_fileData=null;_isReadable=!1;_transformCache=null;_loadingTransform=!1;_currentPath=null;initCallback(){t.addEventListener("file-selected",e=>this._loadFile(e.detail.path));t.addEventListener("follow-focus-changed",e=>{const d=e.detail;if(d.type==="file"&&d.target){this._loadFile(d.target);if(d.meta?.startLine){setTimeout(()=>{const c=this._getCodeBlock();if(c&&c.scrollToLine)c.scrollToLine(d.meta.startLine)},200)}}});if(o.activeFile)requestAnimationFrame(()=>this._loadFile(o.activeFile))}renderCallback(){this.sub("hasFile",e=>{this.toggleAttribute("has-file",e)}),this.sub("viewMode",e=>{
|
|
33
33
|
const lang=_getLang(this._currentPath);
|
|
34
34
|
this.toggleAttribute("mode-raw","source"!==e);
|
|
35
35
|
if(lang==='md'){
|
package/web/panels/dep-graph.js
CHANGED
|
@@ -687,8 +687,6 @@ export class DepGraph extends Symbiote {
|
|
|
687
687
|
_wasDragged = false;
|
|
688
688
|
/** @type {Map<string, string>} */
|
|
689
689
|
_fileMap = new Map();
|
|
690
|
-
/** @type {boolean} */
|
|
691
|
-
_autopilot = false;
|
|
692
690
|
/** @type {HTMLElement|null} */
|
|
693
691
|
_canvas = null;
|
|
694
692
|
/** @type {object|null} Skeleton data for resolving pin names */
|
|
@@ -851,11 +849,6 @@ export class DepGraph extends Symbiote {
|
|
|
851
849
|
this._restoreFlatFocus();
|
|
852
850
|
}
|
|
853
851
|
});
|
|
854
|
-
|
|
855
|
-
// Follow mode: listen for global state (set from topbar)
|
|
856
|
-
events.addEventListener('follow-mode-changed', (e) => {
|
|
857
|
-
this._autopilot = e.detail.enabled;
|
|
858
|
-
});
|
|
859
852
|
|
|
860
853
|
// Label Mode controls
|
|
861
854
|
const labelBtns = this.querySelectorAll('.label-mode-btn');
|
|
@@ -983,17 +976,14 @@ export class DepGraph extends Symbiote {
|
|
|
983
976
|
ro.observe(this);
|
|
984
977
|
this._resizeObserver = ro;
|
|
985
978
|
|
|
986
|
-
// Bind and save global listener functions for clean up
|
|
987
979
|
this._onSkeletonLoaded = (e) => {
|
|
988
980
|
if (this._graphBuilt || this.style.display === 'none' || this.offsetWidth === 0) return;
|
|
989
981
|
requestAnimationFrame(() => this._buildGraph(e.detail));
|
|
990
982
|
};
|
|
991
983
|
|
|
992
|
-
this.
|
|
984
|
+
this._onFollowFocusChanged = (e) => {
|
|
993
985
|
if (this.style.display === 'none' || this.offsetWidth === 0) return;
|
|
994
|
-
|
|
995
|
-
this._handleAutopilot(e.detail);
|
|
996
|
-
}
|
|
986
|
+
this._handleFollowFocus(e.detail);
|
|
997
987
|
};
|
|
998
988
|
|
|
999
989
|
this._onFileSelected = (e) => {
|
|
@@ -1026,8 +1016,8 @@ export class DepGraph extends Symbiote {
|
|
|
1026
1016
|
}).catch(() => {});
|
|
1027
1017
|
}
|
|
1028
1018
|
|
|
1029
|
-
// Autopilot: listen for
|
|
1030
|
-
events.addEventListener('
|
|
1019
|
+
// Autopilot: listen for orchestrator events
|
|
1020
|
+
events.addEventListener('follow-focus-changed', this._onFollowFocusChanged);
|
|
1031
1021
|
|
|
1032
1022
|
// Update route within graph section
|
|
1033
1023
|
// On node click → save file path (just focusing)
|
|
@@ -1165,7 +1155,7 @@ export class DepGraph extends Symbiote {
|
|
|
1165
1155
|
disconnectedCallback() {
|
|
1166
1156
|
super.disconnectedCallback?.();
|
|
1167
1157
|
if (this._onSkeletonLoaded) events.removeEventListener('skeleton-loaded', this._onSkeletonLoaded);
|
|
1168
|
-
if (this.
|
|
1158
|
+
if (this._onFollowFocusChanged) events.removeEventListener('follow-focus-changed', this._onFollowFocusChanged);
|
|
1169
1159
|
if (this._onFileSelected) events.removeEventListener('file-selected', this._onFileSelected);
|
|
1170
1160
|
if (this._onHashChange) window.removeEventListener('hashchange', this._onHashChange);
|
|
1171
1161
|
if (this._resizeObserver) {
|
|
@@ -2498,37 +2488,26 @@ export class DepGraph extends Symbiote {
|
|
|
2498
2488
|
}
|
|
2499
2489
|
|
|
2500
2490
|
/**
|
|
2501
|
-
* Handle
|
|
2502
|
-
* @param {object}
|
|
2491
|
+
* Handle orchestrated visual focus from FollowController
|
|
2492
|
+
* @param {object} detail
|
|
2503
2493
|
*/
|
|
2504
|
-
|
|
2494
|
+
_handleFollowFocus({ type, target, action }) {
|
|
2505
2495
|
if (!this._editor || !this._canvas) return;
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
} else if (toolName === 'navigate' && args.action === 'call_chain') {
|
|
2517
|
-
// Phase 4: animate call chain when agent traces a path
|
|
2518
|
-
if (args.from && args.to) {
|
|
2519
|
-
this._highlightCallChain(args.from, args.to);
|
|
2520
|
-
}
|
|
2521
|
-
} else if (toolName === 'navigate' && args.action === 'usages' && args.symbol) {
|
|
2522
|
-
this._highlightDeps(args.symbol);
|
|
2523
|
-
} else if (toolName === 'get_skeleton') {
|
|
2496
|
+
if (type !== 'graph' && type !== 'file') return; // React to graph and file actions
|
|
2497
|
+
|
|
2498
|
+
if (type === 'graph') {
|
|
2499
|
+
if (action === 'focus' && target) {
|
|
2500
|
+
this._focusSymbol(target);
|
|
2501
|
+
} else if (action === 'deps' && target) {
|
|
2502
|
+
this._highlightDeps(target);
|
|
2503
|
+
} else if (action === 'chain' && target.from && target.to) {
|
|
2504
|
+
this._highlightCallChain(target.from, target.to);
|
|
2505
|
+
} else if (action === 'fit') {
|
|
2524
2506
|
this._canvas.fitView();
|
|
2525
|
-
} else if (toolName === 'compact' && args.path) {
|
|
2526
|
-
this._pulseFile(args.path);
|
|
2527
|
-
} else if (toolName === 'view_file' && args.path) {
|
|
2528
|
-
// Agent opened a file — focus it on the board
|
|
2529
|
-
this._focusFile(args.path);
|
|
2530
|
-
this._pulseFile(args.path);
|
|
2531
2507
|
}
|
|
2508
|
+
} else if (type === 'file' && target) {
|
|
2509
|
+
this._focusFile(target);
|
|
2510
|
+
this._pulseFile(target);
|
|
2532
2511
|
}
|
|
2533
2512
|
}
|
|
2534
2513
|
|
package/web/style.css
CHANGED
|
@@ -123,6 +123,12 @@ html, body {
|
|
|
123
123
|
background: rgba(76, 139, 245, 0.15);
|
|
124
124
|
color: #4c8bf5;
|
|
125
125
|
border-color: rgba(76, 139, 245, 0.3);
|
|
126
|
+
animation: follow-btn-glow 2s ease-in-out infinite;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@keyframes follow-btn-glow {
|
|
130
|
+
0%, 100% { box-shadow: 0 0 4px rgba(76, 139, 245, 0.1); }
|
|
131
|
+
50% { box-shadow: 0 0 12px rgba(76, 139, 245, 0.4); }
|
|
126
132
|
}
|
|
127
133
|
|
|
128
134
|
/* Agent badge */
|