@vesk/adapter 0.2.9 → 0.2.11

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 (42) hide show
  1. package/dist/client-bundle.d.ts +29 -0
  2. package/dist/client-bundle.d.ts.map +1 -1
  3. package/dist/client-bundle.js +333 -52
  4. package/dist/dev-api.d.ts +78 -0
  5. package/dist/dev-api.d.ts.map +1 -0
  6. package/dist/dev-api.js +338 -0
  7. package/dist/dev-config.d.ts +48 -0
  8. package/dist/dev-config.d.ts.map +1 -0
  9. package/dist/dev-config.js +964 -0
  10. package/dist/dev-server.d.ts +85 -0
  11. package/dist/dev-server.d.ts.map +1 -1
  12. package/dist/dev-server.js +329 -8
  13. package/dist/error-codeframe.d.ts +23 -0
  14. package/dist/error-codeframe.d.ts.map +1 -0
  15. package/dist/error-codeframe.js +127 -0
  16. package/dist/error-tips.d.ts +7 -0
  17. package/dist/error-tips.d.ts.map +1 -0
  18. package/dist/error-tips.js +91 -0
  19. package/dist/hmr-utils.d.ts +14 -0
  20. package/dist/hmr-utils.d.ts.map +1 -0
  21. package/dist/hmr-utils.js +56 -0
  22. package/dist/hmr.d.ts +40 -0
  23. package/dist/hmr.d.ts.map +1 -1
  24. package/dist/hmr.js +139 -20
  25. package/dist/index.d.ts +37 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +105 -22
  28. package/dist/paths.d.ts +8 -0
  29. package/dist/paths.d.ts.map +1 -1
  30. package/dist/paths.js +32 -0
  31. package/dist/platform-handler.d.ts.map +1 -1
  32. package/dist/platform-handler.js +2 -1
  33. package/dist/plugins.d.ts +147 -0
  34. package/dist/plugins.d.ts.map +1 -0
  35. package/dist/plugins.js +1109 -0
  36. package/dist/prod-server.d.ts.map +1 -1
  37. package/dist/prod-server.js +43 -9
  38. package/dist/ssr-function.d.ts.map +1 -1
  39. package/dist/ssr-function.js +14 -2
  40. package/dist/types.d.ts +1 -1
  41. package/dist/types.d.ts.map +1 -1
  42. package/package.json +4 -4
@@ -1,7 +1,92 @@
1
1
  import { type Server } from 'node:http';
2
2
  import type { DevServerOptions } from '@vesk/adapter/src/types';
3
+ /**
4
+ * Shape returned by the dev HMR state endpoint. Mirrors what the parallel
5
+ * `hmr.ts` agent's `getHmrState()` returns; we tolerate its absence at
6
+ * module-load time (see `devHmrState`) so this module stays importable even
7
+ * before the state provider lands.
8
+ */
9
+ export interface DevHmrState {
10
+ status: 'up' | 'compiling' | 'error' | 'down';
11
+ lastCompileMs: number | null;
12
+ error: Record<string, unknown> | null;
13
+ hasError: boolean;
14
+ componentCount: number;
15
+ }
16
+ /** Default state when no HMR provider is available (no server). */
17
+ export declare function defaultDevHmrState(): DevHmrState;
3
18
  export declare function bodyTooLarge(maxBytes: number): Error & {
4
19
  status: number;
5
20
  };
21
+ export interface DevPanelResponse {
22
+ status: number;
23
+ headers: Record<string, string>;
24
+ body: string;
25
+ /** When 'base64', the dev server writes Buffer.from(body, 'base64') to the socket (binary payloads like icons). */
26
+ encoding?: 'utf8' | 'base64';
27
+ /** When set, `body`/`encoding` are ignored and this async iterable of
28
+ (already-framed) strings is streamed to the client instead — used for
29
+ SSE agent progress. */
30
+ stream?: AsyncIterable<string>;
31
+ }
32
+ export interface PluginRouterChangeEvent {
33
+ type: 'activate' | 'deactivate' | 'install' | 'uninstall' | 'update';
34
+ name?: string;
35
+ }
36
+ export interface PluginStateRouterOptions {
37
+ appDir: string;
38
+ veskDir: string;
39
+ configPluginNames: string[];
40
+ getHmrState?: () => DevHmrState;
41
+ /** Called after a mutation so the caller can rebuild + notify HMR clients. */
42
+ onPluginChange?: (event: PluginRouterChangeEvent) => void | Promise<void>;
43
+ }
44
+ /**
45
+ * Pure, dependency-injectable router for the dev panel HTTP endpoints
46
+ * (`/__vesk/...`). Everything is testable with fake inputs — no socket, no
47
+ * listener. Returns `null` for paths that are not dev-panel endpoints so the
48
+ * dev server can fall through to its normal route handling.
49
+ *
50
+ * Endpoints:
51
+ * GET /__vesk/hmr/state → { ...DevHmrState }
52
+ * GET /__vesk/plugins → { plugins: PluginRecord[] } (registry-enriched)
53
+ * POST /__vesk/plugins/activate → { ok, record }
54
+ * POST /__vesk/plugins/deactivate → { ok, record }
55
+ * POST /__vesk/plugins/install → { ok, record }
56
+ * POST /__vesk/plugins/uninstall → { ok }
57
+ * POST /__vesk/plugins/update → { ok, record }
58
+ * GET /__vesk/plugins/search?q=<query> → { results: PluginSearchResult[] }
59
+ * GET /__vesk/plugins/:name/icon → { base64 + mime } (or 404)
60
+ * GET /__vesk/plugins/:name/exports → { PluginExportsInfo }
61
+ * (anything else under /__vesk/*) → 404 { error }
62
+ */
63
+ export declare function createPluginStateRouter(opts: PluginStateRouterOptions): {
64
+ route: (method: string, pathname: string, body?: unknown, search?: string) => Promise<DevPanelResponse | null>;
65
+ };
66
+ /**
67
+ * Inline bootstrap script injected after the dev/HMR scripts on every served
68
+ * dev page. On DOMContentLoaded it polls `/__vesk/hmr/state` and — if the
69
+ * server reports a persisted error — calls the client's registered
70
+ * `globalThis.__vesk_hmr_show(payload)` to re-open the error overlay. This
71
+ * satisfies the Nuxt-like "a refresh during an active error should still show
72
+ * the overlay" requirement, because the client bundle's own state fetch only
73
+ * fires after its module runs.
74
+ *
75
+ * Idempotency: the `window.__vesk_hmr_bootstrap` guard makes the inline script
76
+ * run at most once per page even if it is injected at multiple `</body>` sites
77
+ * (or on a page served more than once). The overlay itself cannot be duplicated
78
+ * because the client's `showOverlay` reuses a single `#__vesk_overlay` element
79
+ * (guarded in `createOverlay`), and `__vesk_hmr_show` is registered once by
80
+ * `registerGlobalHmr`. So a double-show (inline bootstrap + client's own
81
+ * `loadPersistedState`) re-renders the same overlay rather than stacking a
82
+ * second one.
83
+ */
84
+ export declare function devBootstrapScript(): string;
85
+ /**
86
+ * Inject the dev/HMR client script plus the inline state bootstrap into an HTML
87
+ * page at its first `</body>`. Idempotent when there is no `</body>`: the HTML
88
+ * is returned unchanged (never injected twice / never appended uncontrolled).
89
+ */
90
+ export declare function injectDevScripts(html: string): string;
6
91
  export declare function startDevServer(appDir: string, options?: DevServerOptions): Promise<Server | void>;
7
92
  //# sourceMappingURL=dev-server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../src/dev-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,MAAM,EAA6C,MAAM,WAAW,CAAC;AAQjG,OAAO,KAAK,EAAa,gBAAgB,EAAY,MAAM,yBAAyB,CAAC;AAWrF,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAIzE;AA6DD,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoTvG"}
1
+ {"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../src/dev-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,MAAM,EAA6C,MAAM,WAAW,CAAC;AAUjG,OAAO,KAAK,EAAa,gBAAgB,EAAwB,MAAM,yBAAyB,CAAC;AAgBjG;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,IAAI,GAAG,WAAW,GAAG,OAAO,GAAG,MAAM,CAAC;IAC9C,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACtC,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,mEAAmE;AACnE,wBAAgB,kBAAkB,IAAI,WAAW,CAEhD;AAuCD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAIzE;AAkBD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,mHAAmH;IACnH,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC7B;;8BAE0B;IAC1B,MAAM,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,UAAU,GAAG,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;IACrE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,WAAW,CAAC;IAChC,8EAA8E;IAC9E,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3E;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,wBAAwB,GAAG;IACvE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;CAChH,CAUA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAa3C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIrD;AA6CD,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoevG"}
@@ -5,10 +5,55 @@ import { fileURLToPath } from 'node:url';
5
5
  import { stripCodeTypes } from '@vesk/compiler/src/strip-ts';
6
6
  import { DEFAULT_MAX_BODY_BYTES } from '@vesk/compiler/src/server-codegen';
7
7
  import { build } from '@vesk/adapter/src/index';
8
- import { createHmrServer } from '@vesk/adapter/src/hmr';
8
+ import { createHmrServer } from './hmr';
9
+ import * as hmrApi from './hmr';
10
+ import { createDevApiRouter } from './dev-api';
9
11
  import { buildRuntimeCode } from '@vesk/adapter/src/client-bundle';
10
- import { resolveWithin } from '@vesk/adapter/src/paths';
12
+ import { resolveWithin, installMdReadHook } from '@vesk/adapter/src/paths';
13
+ import { getPluginRecords } from './plugins';
14
+ // @vesk/agentic — optional AI plugin; gated by active state if installed
15
+ import { createAgentRouter } from '@vesk/agentic/src/dev-api';
16
+ import { CheckpointManager } from '@vesk/agentic/src/checkpoints';
17
+ import { AgentCapabilityTable } from '@vesk/agentic/src/permissions';
18
+ import { openAiProvider } from '@vesk/agentic/src/providers/openai';
19
+ import { anthropicProvider } from '@vesk/agentic/src/providers/anthropic';
20
+ import { Agent } from '@vesk/agentic/src/loop';
21
+ import { createVeskTools } from '@vesk/agentic/src/tools/vesk';
22
+ import { getApiKey, loadAgenticConfig, SUPPORTED_PROVIDERS } from '@vesk/agentic/src/config';
11
23
  const __dirname = dirname(fileURLToPath(import.meta.url));
24
+ /** Default state when no HMR provider is available (no server). */
25
+ export function defaultDevHmrState() {
26
+ return { status: 'up', lastCompileMs: null, error: null, hasError: false, componentCount: 0 };
27
+ }
28
+ /** The live HMR state provider: the parallel agent's `getHmrState` if present, else a safe no-server default. */
29
+ const devHmrState = typeof hmrApi.getHmrState === 'function'
30
+ ? hmrApi.getHmrState
31
+ : defaultDevHmrState;
32
+ /**
33
+ * Writes a handler Response to the socket, piping a streaming body
34
+ * chunk-by-chunk (SSE / text streams) instead of buffering everything.
35
+ */
36
+ async function deliverResponse(res, response) {
37
+ res.writeHead(response.status, Object.fromEntries(response.headers));
38
+ const body = response.body;
39
+ if (body && typeof body.getReader === 'function') {
40
+ const reader = body.getReader();
41
+ try {
42
+ for (;;) {
43
+ const { done, value } = await reader.read();
44
+ if (done)
45
+ break;
46
+ res.write(Buffer.from(value));
47
+ }
48
+ }
49
+ catch {
50
+ /* stream aborted by the client */
51
+ }
52
+ res.end();
53
+ return;
54
+ }
55
+ res.end(await response.text());
56
+ }
12
57
  export function bodyTooLarge(maxBytes) {
13
58
  const err = new Error(`Request body exceeds limit (${maxBytes} bytes)`);
14
59
  err.status = 413;
@@ -29,6 +74,77 @@ async function readBody(req, maxBytes = DEFAULT_MAX_BODY_BYTES) {
29
74
  }
30
75
  return Buffer.concat(chunks);
31
76
  }
77
+ /**
78
+ * Pure, dependency-injectable router for the dev panel HTTP endpoints
79
+ * (`/__vesk/...`). Everything is testable with fake inputs — no socket, no
80
+ * listener. Returns `null` for paths that are not dev-panel endpoints so the
81
+ * dev server can fall through to its normal route handling.
82
+ *
83
+ * Endpoints:
84
+ * GET /__vesk/hmr/state → { ...DevHmrState }
85
+ * GET /__vesk/plugins → { plugins: PluginRecord[] } (registry-enriched)
86
+ * POST /__vesk/plugins/activate → { ok, record }
87
+ * POST /__vesk/plugins/deactivate → { ok, record }
88
+ * POST /__vesk/plugins/install → { ok, record }
89
+ * POST /__vesk/plugins/uninstall → { ok }
90
+ * POST /__vesk/plugins/update → { ok, record }
91
+ * GET /__vesk/plugins/search?q=<query> → { results: PluginSearchResult[] }
92
+ * GET /__vesk/plugins/:name/icon → { base64 + mime } (or 404)
93
+ * GET /__vesk/plugins/:name/exports → { PluginExportsInfo }
94
+ * (anything else under /__vesk/*) → 404 { error }
95
+ */
96
+ export function createPluginStateRouter(opts) {
97
+ // Delegates to the unified DevTools router (dev-api.ts) for the plugin +
98
+ // HMR-state surface, preserving the exact wire contract.
99
+ return createDevApiRouter({
100
+ appDir: opts.appDir,
101
+ veskDir: opts.veskDir,
102
+ configPluginNames: opts.configPluginNames,
103
+ getHmrState: opts.getHmrState,
104
+ onPluginChange: opts.onPluginChange,
105
+ });
106
+ }
107
+ /**
108
+ * Inline bootstrap script injected after the dev/HMR scripts on every served
109
+ * dev page. On DOMContentLoaded it polls `/__vesk/hmr/state` and — if the
110
+ * server reports a persisted error — calls the client's registered
111
+ * `globalThis.__vesk_hmr_show(payload)` to re-open the error overlay. This
112
+ * satisfies the Nuxt-like "a refresh during an active error should still show
113
+ * the overlay" requirement, because the client bundle's own state fetch only
114
+ * fires after its module runs.
115
+ *
116
+ * Idempotency: the `window.__vesk_hmr_bootstrap` guard makes the inline script
117
+ * run at most once per page even if it is injected at multiple `</body>` sites
118
+ * (or on a page served more than once). The overlay itself cannot be duplicated
119
+ * because the client's `showOverlay` reuses a single `#__vesk_overlay` element
120
+ * (guarded in `createOverlay`), and `__vesk_hmr_show` is registered once by
121
+ * `registerGlobalHmr`. So a double-show (inline bootstrap + client's own
122
+ * `loadPersistedState`) re-renders the same overlay rather than stacking a
123
+ * second one.
124
+ */
125
+ export function devBootstrapScript() {
126
+ return ("<script>\n" +
127
+ "if(!window.__vesk_hmr_bootstrap){window.__vesk_hmr_bootstrap=1;" +
128
+ "(function(){function boot(){fetch('/__vesk/hmr/state')" +
129
+ ".then(function(r){return r.ok?r.json():null;}).then(function(s){" +
130
+ "if(s&&s.error&&window.__vesk_hmr_show){window.__vesk_hmr_show(s.error);}" +
131
+ "}).catch(function(){});}" +
132
+ "if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',boot);}else{boot();}" +
133
+ "})();" +
134
+ "}\n" +
135
+ "</script>");
136
+ }
137
+ /**
138
+ * Inject the dev/HMR client script plus the inline state bootstrap into an HTML
139
+ * page at its first `</body>`. Idempotent when there is no `</body>`: the HTML
140
+ * is returned unchanged (never injected twice / never appended uncontrolled).
141
+ */
142
+ export function injectDevScripts(html) {
143
+ if (!html.includes('</body>'))
144
+ return html;
145
+ const scripts = '\t<script type="module" src="/_vesk/hmr.js"></script>\n' + devBootstrapScript() + '\n';
146
+ return html.replace('</body>', scripts + '</body>');
147
+ }
32
148
  function makeWebRequest(nodeReq, url, maxBodyBytes = DEFAULT_MAX_BODY_BYTES) {
33
149
  const parsedUrl = new URL(url, `http://${nodeReq.headers.host || 'localhost'}`);
34
150
  const method = nodeReq.method || 'GET';
@@ -86,6 +202,139 @@ export async function startDevServer(appDir, options) {
86
202
  process.env.NODE_ENV = 'development';
87
203
  const devDir = resolve(appDir, '..', '.vesk', 'dev');
88
204
  const publicDir = options?.publicDir || resolve(appDir, '..', 'public');
205
+ installMdReadHook([publicDir, resolve(devDir, 'static', 'public')]);
206
+ // Dev-panel plugin list: plugin / module names declared in the dev server's own config.
207
+ const configPluginNames = (options?.plugins || []).map((p) => {
208
+ if (typeof p === 'string')
209
+ return p;
210
+ return p.name;
211
+ }).filter((n) => typeof n === 'string' && !!n);
212
+ // ── @vesk/agentic dev API (B6-plug) — chained BEFORE the core dev API ─────
213
+ // Chain: createAgentRouter (CheckpointManager + AgentCapabilityTable + Provider
214
+ // via openAiProvider/anthropicProvider, .env.local VK_{PROVIDER}_KEY) →
215
+ // createDevApiRouter. Gate: only expose /__vesk/agent/* when @vesk/agentic
216
+ // is installed AND active (otherwise lean core, no agent surface).
217
+ const agenticCheckpointManager = new CheckpointManager();
218
+ const agenticVeskDir = resolve(appDir, '..', '.vesk');
219
+ const agenticProjectDir = resolve(appDir, '..');
220
+ function isAgenticActive() {
221
+ try {
222
+ if (SUPPORTED_PROVIDERS.some((p) => !!getApiKey(agenticProjectDir, p)))
223
+ return true;
224
+ }
225
+ catch { }
226
+ try {
227
+ const records = getPluginRecords(appDir, agenticVeskDir, configPluginNames);
228
+ const rec = records.find((r) => r.name === '@vesk/agentic' || r.package === '@vesk/agentic');
229
+ if (!rec)
230
+ return false;
231
+ return rec.active;
232
+ }
233
+ catch {
234
+ return false;
235
+ }
236
+ }
237
+ function getAgenticPermissions() {
238
+ const rawMode = process.env.VESK_AGENTIC_MODE || 'explore';
239
+ const valid = ['explore', 'debug', 'agent'];
240
+ const mode = (valid.includes(rawMode) ? rawMode : 'explore');
241
+ return new AgentCapabilityTable(mode);
242
+ }
243
+ async function runAgenticAgent(prompt, _mode, providerConfig) {
244
+ const cfg = (providerConfig || {});
245
+ const provider = await buildAdapterAgenticProvider(cfg);
246
+ const tools = createVeskTools({ projectDir: agenticProjectDir, appDir, veskDir: agenticVeskDir });
247
+ const budget = resolveAdapterAgenticStepBudget(cfg);
248
+ const agent = new Agent({ provider, tools, maxSteps: budget.maxSteps, autoExtend: budget.autoExtend, hardMaxSteps: budget.hardMaxSteps });
249
+ return agent.run(prompt);
250
+ }
251
+ async function* runAgenticAgentStream(prompt, _mode, providerConfig) {
252
+ const cfg = (providerConfig || {});
253
+ const provider = await buildAdapterAgenticProvider(cfg);
254
+ const tools = createVeskTools({ projectDir: agenticProjectDir, appDir, veskDir: agenticVeskDir });
255
+ const budget = resolveAdapterAgenticStepBudget(cfg);
256
+ const agent = new Agent({ provider, tools, maxSteps: budget.maxSteps, autoExtend: budget.autoExtend, hardMaxSteps: budget.hardMaxSteps });
257
+ yield* agent.runStream(prompt);
258
+ }
259
+ async function buildAdapterAgenticProvider(cfg) {
260
+ const providerName = String(cfg.provider || loadAgenticConfig(agenticProjectDir).provider || 'openai');
261
+ const apiKey = cfg.apiKey || getApiKey(agenticProjectDir, providerName) || '';
262
+ const model = cfg.model || loadAgenticConfig(agenticProjectDir).model || (providerName === 'anthropic' ? 'claude-sonnet-4-6' : providerName === 'google' ? 'gemini-2.0-flash' : providerName === 'ollama' ? 'llama3.1' : providerName === 'opencode' ? 'claude-sonnet-4-6' : providerName === 'opencode-go' ? 'opencode-go/kimi-k3' : providerName === 'openrouter' ? 'openrouter/auto' : 'gpt-4o-mini');
263
+ const baseUrl = cfg.baseUrl || loadAgenticConfig(agenticProjectDir).baseUrl || undefined;
264
+ const maxTokens = typeof cfg.maxTokens === 'number' ? cfg.maxTokens : undefined;
265
+ if (providerName === 'anthropic')
266
+ return anthropicProvider({ apiKey, model, baseUrl, maxTokens });
267
+ if (providerName === 'google') {
268
+ const { googleProvider } = await import('@vesk/agentic/src/providers/google');
269
+ return googleProvider({ apiKey, model, baseUrl });
270
+ }
271
+ if (providerName === 'ollama') {
272
+ const { ollamaProvider } = await import('@vesk/agentic/src/providers/ollama');
273
+ return ollamaProvider({ model, baseUrl });
274
+ }
275
+ if (providerName === 'opencode')
276
+ return openAiProvider({ apiKey, model, baseUrl: baseUrl || 'https://opencode.ai/zen/v1' });
277
+ if (providerName === 'opencode-go')
278
+ return openAiProvider({ apiKey, model, baseUrl: baseUrl || 'https://opencode.ai/zen/go/v1' });
279
+ if (providerName === 'openrouter')
280
+ return openAiProvider({ apiKey, model, baseUrl: baseUrl || 'https://openrouter.ai/api/v1' });
281
+ if (providerName === 'loopers')
282
+ return openAiProvider({ apiKey, model, baseUrl: baseUrl || 'http://localhost:8080' });
283
+ return openAiProvider({ apiKey, model, baseUrl });
284
+ }
285
+ function resolveAdapterAgenticStepBudget(cfg) {
286
+ const fileCfg = loadAgenticConfig(agenticProjectDir);
287
+ const requestVal = typeof cfg.maxSteps === 'number' && Number.isFinite(cfg.maxSteps) ? cfg.maxSteps : NaN;
288
+ const fileVal = typeof fileCfg.maxSteps === 'number' && fileCfg.maxSteps > 0 ? fileCfg.maxSteps : NaN;
289
+ const maxSteps = Number.isFinite(requestVal) ? Math.max(1, Math.floor(requestVal)) : Number.isFinite(fileVal) ? Math.floor(fileVal) : 25;
290
+ return { maxSteps, autoExtend: true, hardMaxSteps: 200 };
291
+ }
292
+ let agentRouter = null;
293
+ try {
294
+ if (isAgenticActive()) {
295
+ agentRouter = createAgentRouter({
296
+ projectDir: agenticProjectDir,
297
+ appDir,
298
+ veskDir: agenticVeskDir,
299
+ getPermissions: getAgenticPermissions,
300
+ runAgent: runAgenticAgent,
301
+ runAgentStream: runAgenticAgentStream,
302
+ listCheckpoints: () => agenticCheckpointManager.listNewestFirst(),
303
+ rollback: (id) => agenticCheckpointManager.get(id) ?? null,
304
+ createCheckpoint: (...args) => {
305
+ const first = args[0];
306
+ if (first && typeof first === 'object' && ('label' in first || 'message' in first)) {
307
+ return agenticCheckpointManager.create(first);
308
+ }
309
+ const msg = typeof first === 'string' ? first : 'checkpoint';
310
+ return agenticCheckpointManager.create({ label: msg, message: msg });
311
+ },
312
+ });
313
+ }
314
+ }
315
+ catch {
316
+ agentRouter = null;
317
+ }
318
+ // Late-bound so the router's onPluginChange can broadcast over the HMR
319
+ // WebSocket, which is only created further down in this function.
320
+ let hmrSession = null;
321
+ const devPanel = createPluginStateRouter({
322
+ appDir,
323
+ veskDir: resolve(appDir, '..', '.vesk'),
324
+ configPluginNames,
325
+ getHmrState: devHmrState,
326
+ onPluginChange: async (event) => {
327
+ try {
328
+ await doBuild();
329
+ if (hmrSession) {
330
+ hmrSession.broadcast('reload', { reason: `Plugin ${event.type}: ${event.name ?? ''}`, time: Date.now() });
331
+ }
332
+ }
333
+ catch {
334
+ /* plugin change rebuild is best-effort; the server keeps serving */
335
+ }
336
+ },
337
+ });
89
338
  let componentMap = new Map();
90
339
  const monorepoRouter = resolve(__dirname, '..', '..', 'compiler', 'dist', 'router.js');
91
340
  const pkgRouter = resolve(appDir, '..', 'node_modules', '@vesk/compiler', 'router.js');
@@ -126,6 +375,79 @@ export async function startDevServer(appDir, options) {
126
375
  await doBuild();
127
376
  const server = createServer(async (req, res) => {
128
377
  const url = new URL(req.url || '/', `http://localhost:${port}`);
378
+ // Dev panel endpoints (/__vesk/...): HMR state, plugin list, activate/
379
+ // deactivate/install/uninstall. Routed through the pure injectable router.
380
+ // Chain: agent router (CheckpointManager + AgentCapabilityTable + Provider
381
+ // via openAiProvider/anthropicProvider, .env.local VK_{PROVIDER}_KEY)
382
+ // is tried BEFORE the core dev panel, gated by @vesk/agentic active.
383
+ if (url.pathname.startsWith('/__vesk/')) {
384
+ let body = undefined;
385
+ if ((req.method || 'GET') === 'POST') {
386
+ try {
387
+ const buf = await readBody(req, maxBodyBytes);
388
+ body = buf.length > 0 ? JSON.parse(buf.toString()) : {};
389
+ }
390
+ catch (e) {
391
+ res.writeHead(400, { 'Content-Type': 'application/json' });
392
+ res.end(JSON.stringify({ error: e instanceof Error ? e.message : String(e) }));
393
+ return;
394
+ }
395
+ }
396
+ if (agentRouter) {
397
+ const agentResult = await agentRouter.route(req.method || 'GET', url.pathname, body, url.search);
398
+ if (agentResult) {
399
+ if (agentResult.stream) {
400
+ res.writeHead(agentResult.status, agentResult.headers);
401
+ res.flushHeaders?.();
402
+ void (async () => {
403
+ try {
404
+ for await (const chunk of agentResult.stream) {
405
+ if (!res.writableEnded)
406
+ res.write(chunk);
407
+ }
408
+ }
409
+ catch {
410
+ /* stream aborted */
411
+ }
412
+ finally {
413
+ if (!res.writableEnded)
414
+ res.end();
415
+ }
416
+ })();
417
+ return;
418
+ }
419
+ res.writeHead(agentResult.status, agentResult.headers);
420
+ res.end(agentResult.encoding === 'base64' ? Buffer.from(agentResult.body, 'base64') : agentResult.body);
421
+ return;
422
+ }
423
+ }
424
+ const result = await devPanel.route(req.method || 'GET', url.pathname, body, url.search);
425
+ if (result) {
426
+ if (result.stream) {
427
+ res.writeHead(result.status, result.headers);
428
+ res.flushHeaders?.();
429
+ void (async () => {
430
+ try {
431
+ for await (const chunk of result.stream) {
432
+ if (!res.writableEnded)
433
+ res.write(chunk);
434
+ }
435
+ }
436
+ catch {
437
+ /* stream aborted */
438
+ }
439
+ finally {
440
+ if (!res.writableEnded)
441
+ res.end();
442
+ }
443
+ })();
444
+ return;
445
+ }
446
+ res.writeHead(result.status, result.headers);
447
+ res.end(result.encoding === 'base64' ? Buffer.from(result.body, 'base64') : result.body);
448
+ return;
449
+ }
450
+ }
129
451
  if (url.pathname === '/_vesk/hmr.js') {
130
452
  const monorepoRoot = resolve(__dirname, '..', '..', '..');
131
453
  const runtimeSrc = resolve(monorepoRoot, 'packages', 'runtime', 'dist');
@@ -195,7 +517,7 @@ export async function startDevServer(appDir, options) {
195
517
  const contentType = headers['content-type'] || '';
196
518
  let finalBody = body;
197
519
  if (contentType.includes('text/html')) {
198
- finalBody = body.replace('</body>', '\t<script type="module" src="/_vesk/hmr.js"></script>\n</body>');
520
+ finalBody = injectDevScripts(body);
199
521
  }
200
522
  res.writeHead(response.status, headers);
201
523
  res.end(finalBody);
@@ -223,9 +545,7 @@ export async function startDevServer(appDir, options) {
223
545
  const mod = await import(`${handlerPath}?t=${ssrVersion}`);
224
546
  const webRequest = makeWebRequest(req, url.href, maxBodyBytes);
225
547
  const response = await mod.handle(webRequest);
226
- const body = await response.text();
227
- res.writeHead(response.status, Object.fromEntries(response.headers));
228
- res.end(body);
548
+ await deliverResponse(res, response);
229
549
  }
230
550
  catch (e) {
231
551
  const message = e instanceof Error ? e.message : String(e);
@@ -250,7 +570,7 @@ export async function startDevServer(appDir, options) {
250
570
  const contentType = headers['content-type'] || '';
251
571
  let finalBody = body;
252
572
  if (contentType.includes('text/html')) {
253
- finalBody = body.replace('</body>', '\t<script type="module" src="/_vesk/hmr.js"></script>\n</body>');
573
+ finalBody = injectDevScripts(body);
254
574
  }
255
575
  res.writeHead(response.status, headers);
256
576
  res.end(finalBody);
@@ -277,7 +597,7 @@ export async function startDevServer(appDir, options) {
277
597
  const contentType = headers['content-type'] || '';
278
598
  let finalBody = body;
279
599
  if (contentType.includes('text/html')) {
280
- finalBody = body.replace('</body>', '\t<script type="module" src="/_vesk/hmr.js"></script>\n</body>');
600
+ finalBody = injectDevScripts(body);
281
601
  }
282
602
  res.writeHead(200, headers);
283
603
  res.end(finalBody);
@@ -294,6 +614,7 @@ export async function startDevServer(appDir, options) {
294
614
  res.end('<!DOCTYPE html><html><body><h1>404</h1><p>Not Found</p></body></html>');
295
615
  });
296
616
  const hmr = createHmrServer(server, appDir, devDir, componentMap);
617
+ hmrSession = hmr;
297
618
  const srcDir = resolve(appDir, '..', 'src');
298
619
  try {
299
620
  if (existsSync(srcDir)) {
@@ -0,0 +1,23 @@
1
+ export interface CodeLine {
2
+ no: number;
3
+ text: string;
4
+ isError: boolean;
5
+ }
6
+ export interface Codeframe {
7
+ file: string;
8
+ line: number;
9
+ column: number;
10
+ context: number;
11
+ code: CodeLine[];
12
+ }
13
+ export declare function buildCodeframe(src: string, line: number, column?: number, context?: number): Codeframe | null;
14
+ export interface ErroredLocation {
15
+ file: string;
16
+ line: number | null;
17
+ column: number | null;
18
+ message: string;
19
+ stack: string | null;
20
+ codeframe: Codeframe | null;
21
+ }
22
+ export declare function parseCompilerError(err: unknown, file: string, src?: string): ErroredLocation | null;
23
+ //# sourceMappingURL=error-codeframe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-codeframe.d.ts","sourceRoot":"","sources":["../src/error-codeframe.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,QAAQ,EAAE,CAAC;CAClB;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,SAAI,GAAG,SAAS,GAAG,IAAI,CAcxG;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;CAC7B;AAuBD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAwFnG"}
@@ -0,0 +1,127 @@
1
+ export function buildCodeframe(src, line, column, context = 5) {
2
+ if (typeof src !== 'string' || src.length === 0)
3
+ return null;
4
+ if (typeof line !== 'number' || !Number.isInteger(line))
5
+ return null;
6
+ const lines = src.split('\n');
7
+ if (line < 1 || line > lines.length)
8
+ return null;
9
+ const col = typeof column === 'number' && Number.isInteger(column) && column >= 1 ? column : 1;
10
+ const window = typeof context === 'number' && Number.isFinite(context) && context >= 0 ? context : 5;
11
+ const start = Math.max(1, line - window);
12
+ const end = Math.min(lines.length, line + window);
13
+ const code = [];
14
+ for (let i = start; i <= end; i++) {
15
+ code.push({ no: i, text: lines[i - 1] ?? '', isError: i === line });
16
+ }
17
+ return { file: '', line, column: col, context: window, code };
18
+ }
19
+ const LOCATION_RE = /\((\d+):(\d+)\)/;
20
+ function asRecord(v) {
21
+ if (typeof v === 'object' && v !== null)
22
+ return v;
23
+ return null;
24
+ }
25
+ function asNumber(v) {
26
+ if (typeof v === 'number' && Number.isFinite(v))
27
+ return Math.trunc(v);
28
+ return null;
29
+ }
30
+ function parseEmbeddedLocation(message) {
31
+ const m = message.match(LOCATION_RE);
32
+ if (!m)
33
+ return null;
34
+ const line = parseInt(m[1], 10);
35
+ const column = parseInt(m[2], 10);
36
+ if (!Number.isFinite(line) || !Number.isFinite(column))
37
+ return null;
38
+ return { line, column: column + 1 };
39
+ }
40
+ export function parseCompilerError(err, file, src) {
41
+ if (err === undefined || err === null)
42
+ return null;
43
+ const record = asRecord(err);
44
+ let message;
45
+ let stack = null;
46
+ if (record && typeof record.message === 'string') {
47
+ message = record.message;
48
+ if (typeof record.stack === 'string')
49
+ stack = record.stack;
50
+ }
51
+ else if (typeof err === 'string') {
52
+ message = err;
53
+ }
54
+ else {
55
+ try {
56
+ message = String(err);
57
+ }
58
+ catch {
59
+ message = 'Unknown compiler error';
60
+ }
61
+ const asAny = err;
62
+ if (asAny && typeof asAny.stack === 'string')
63
+ stack = asAny.stack;
64
+ }
65
+ let line = null;
66
+ let column = null;
67
+ let fileResolved = typeof file === 'string' ? file : '';
68
+ // acorn raises VeskError with `.loc { line, column }` (column 0-based) and
69
+ // other codegen errors carry `.position` / `.pos` plus `.line`/`.column`
70
+ // fields on the VeskError class itself. Prefer explicit fields.
71
+ if (record) {
72
+ const loc = asRecord(record.loc);
73
+ if (loc) {
74
+ const ln = asNumber(loc.line);
75
+ if (ln !== null) {
76
+ line = ln;
77
+ const rawCol = asNumber(loc.column);
78
+ column = rawCol !== null ? rawCol + 1 : 1;
79
+ }
80
+ }
81
+ if (line === null) {
82
+ const pos = asRecord(record.position);
83
+ if (pos) {
84
+ const ln = asNumber(pos.line);
85
+ if (ln !== null) {
86
+ line = ln;
87
+ const rawCol = asNumber(pos.column);
88
+ column = rawCol !== null ? rawCol + 1 : 1;
89
+ }
90
+ }
91
+ }
92
+ if (line === null) {
93
+ const ln = asNumber(record.line);
94
+ if (ln !== null && ln > 0) {
95
+ line = ln;
96
+ const rawCol = asNumber(record.column);
97
+ if (rawCol !== null)
98
+ column = rawCol;
99
+ }
100
+ }
101
+ if (typeof record.file === 'string' && record.file)
102
+ fileResolved = record.file;
103
+ }
104
+ // Fall back to the "(line:column)" embedded in the message text (column is
105
+ // 0-based in VeskError messages, matching acorn's loc).
106
+ if (line === null) {
107
+ const embedded = parseEmbeddedLocation(message);
108
+ if (embedded) {
109
+ line = embedded.line;
110
+ column = embedded.column;
111
+ }
112
+ }
113
+ let codeframe = null;
114
+ if (src !== undefined && typeof src === 'string' && src.length > 0 && line !== null) {
115
+ codeframe = buildCodeframe(src, line, column ?? 1);
116
+ if (codeframe)
117
+ codeframe.file = fileResolved;
118
+ }
119
+ return {
120
+ file: fileResolved,
121
+ line,
122
+ column,
123
+ message,
124
+ stack,
125
+ codeframe,
126
+ };
127
+ }
@@ -0,0 +1,7 @@
1
+ export interface ErrorTips {
2
+ tips: string[];
3
+ suggestions: string[];
4
+ nextSteps: string[];
5
+ }
6
+ export declare function suggestFor(message: string): ErrorTips;
7
+ //# sourceMappingURL=error-tips.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-tips.d.ts","sourceRoot":"","sources":["../src/error-tips.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AA8FD,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAMrD"}