@vibeframe/cli 0.106.9 → 0.107.1
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/dist/commands/_shared/compose-prompts.d.ts +8 -1
- package/dist/commands/_shared/compose-scenes-skills.d.ts +10 -1
- package/dist/commands/_shared/root-sync.d.ts +19 -0
- package/dist/commands/_shared/scene-lint.d.ts +13 -2
- package/dist/commands/_shared/status-jobs.d.ts +26 -1
- package/dist/commands/_shared/sub-comp-duration-sync.d.ts +57 -0
- package/dist/commands/_shared/sub-comp-duration-sync.test.d.ts +1 -0
- package/dist/index.js +2073 -1578
- package/dist/tools/_shared/long-poll.d.ts +51 -0
- package/dist/tools/_shared/long-poll.test.d.ts +1 -0
- package/dist/tools/adapters/mcp.d.ts +30 -1
- package/dist/tools/adapters/mcp.js +43 -3
- package/dist/tools/adapters/mcp.test.d.ts +1 -0
- package/dist/tools/define-tool.d.ts +12 -0
- package/dist/tools/manifest/index.js +31926 -31442
- package/dist/tools/manifest/scene.d.ts +2 -2
- package/dist/tools/manifest/storyboard.d.ts +2 -2
- package/dist/utils/api-key.d.ts +5 -2
- package/dist/utils/concurrency.d.ts +11 -0
- package/dist/utils/concurrency.test.d.ts +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module tools/_shared/long-poll
|
|
3
|
+
* @description Long-poll promotion for long-running MCP tool calls.
|
|
4
|
+
*
|
|
5
|
+
* MCP hosts (notably Claude Desktop) time out tool calls after a minute or
|
|
6
|
+
* two, while `build`/`render` legitimately run for many minutes. The fix:
|
|
7
|
+
* run the work normally, and if it finishes inside the synchronous window
|
|
8
|
+
* return its result untouched — byte-identical to the unwrapped call, and no
|
|
9
|
+
* job record is ever written. If the window elapses first, persist a local
|
|
10
|
+
* {@link JobRecord} (jobType "build"/"render", provider "local"), return a
|
|
11
|
+
* `promoted` envelope immediately, and let the work continue in-process; the
|
|
12
|
+
* settle handlers write the terminal record that `status_job` then serves.
|
|
13
|
+
*
|
|
14
|
+
* CLI surfaces never reach this module — callers gate on
|
|
15
|
+
* `ctx.surface === "mcp"`.
|
|
16
|
+
*/
|
|
17
|
+
import type { ToolExecuteResult } from "../define-tool.js";
|
|
18
|
+
export interface LocalJobUpdate {
|
|
19
|
+
/** 0-100 when a percentage is known; otherwise omit. */
|
|
20
|
+
progress?: number;
|
|
21
|
+
/** Pipeline stage label, e.g. "assets" | "compose" | "render". */
|
|
22
|
+
stage?: string;
|
|
23
|
+
message?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface PromotionOptions {
|
|
26
|
+
jobType: "build" | "render";
|
|
27
|
+
/** Absolute project dir — the job record lands in `<projectDir>/.vibeframe/jobs/`. */
|
|
28
|
+
projectDir: string;
|
|
29
|
+
/** CLI equivalent of this invocation, stored on the record for retryWith. */
|
|
30
|
+
command: string;
|
|
31
|
+
/** Synchronous window before promotion. Default 45s; see resolveWindowMs. */
|
|
32
|
+
windowMs?: number;
|
|
33
|
+
/** Heartbeat cadence once promoted. Default 10s. */
|
|
34
|
+
heartbeatMs?: number;
|
|
35
|
+
now?: () => Date;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Resolve the promotion window. `VIBE_MCP_PROMOTE_AFTER_MS` overrides the
|
|
39
|
+
* default; `"off"` or `"0"` disables promotion entirely (pure synchronous
|
|
40
|
+
* behavior — the escape hatch for hosts with generous timeouts).
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveWindowMs(optionMs?: number): number | null;
|
|
43
|
+
/**
|
|
44
|
+
* Run `work`; promote to a polled background job if it outlives the window.
|
|
45
|
+
*
|
|
46
|
+
* The `report` callback passed to `work` is safe to call at any time: before
|
|
47
|
+
* promotion it only buffers the latest update (callers typically also feed
|
|
48
|
+
* `ctx.onProgress` for MCP progress notifications); after promotion it
|
|
49
|
+
* coalesces updates into the job record on disk.
|
|
50
|
+
*/
|
|
51
|
+
export declare function runWithMcpPromotion(work: (report: (update: LocalJobUpdate) => void) => Promise<ToolExecuteResult>, opts: PromotionOptions): Promise<ToolExecuteResult>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -22,12 +22,41 @@ export interface McpTool {
|
|
|
22
22
|
description: string;
|
|
23
23
|
inputSchema: McpInputSchema;
|
|
24
24
|
}
|
|
25
|
-
|
|
25
|
+
/** Per-call extras forwarded by the MCP server request handler. */
|
|
26
|
+
export interface McpCallExtra {
|
|
27
|
+
/**
|
|
28
|
+
* Raw progress sink — typically sends an MCP `notifications/progress` for
|
|
29
|
+
* the request's progressToken. The dispatcher wraps it with throttling,
|
|
30
|
+
* monotonicity, and post-completion cutoff before exposing it to tools.
|
|
31
|
+
*/
|
|
32
|
+
onProgress?: (update: {
|
|
33
|
+
progress: number;
|
|
34
|
+
total?: number;
|
|
35
|
+
message?: string;
|
|
36
|
+
}) => void;
|
|
37
|
+
}
|
|
38
|
+
export type McpDispatcher = (name: string, args: Record<string, unknown>, extra?: McpCallExtra) => Promise<{
|
|
26
39
|
content: Array<{
|
|
27
40
|
type: "text";
|
|
28
41
|
text: string;
|
|
29
42
|
}>;
|
|
30
43
|
}>;
|
|
44
|
+
export interface ProgressSink {
|
|
45
|
+
send: (update: {
|
|
46
|
+
progress?: number;
|
|
47
|
+
total?: number;
|
|
48
|
+
message?: string;
|
|
49
|
+
}) => void;
|
|
50
|
+
close: () => void;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Wrap a raw progress callback with the guarantees the MCP spec expects:
|
|
54
|
+
* `progress` increases monotonically per token, updates are throttled to at
|
|
55
|
+
* most one per `minIntervalMs` (the first always passes), and nothing is
|
|
56
|
+
* emitted after `close()` — progress notifications must stop once the
|
|
57
|
+
* request's result has been sent.
|
|
58
|
+
*/
|
|
59
|
+
export declare function makeProgressSink(raw: McpCallExtra["onProgress"], minIntervalMs?: number): ProgressSink;
|
|
31
60
|
/** Filter manifest by `surfaces.mcp` (default: included) and project to MCP tool shape. */
|
|
32
61
|
export declare function manifestToMcpTools(manifest: readonly ToolDefinition[]): McpTool[];
|
|
33
62
|
/** Build the dispatcher used by `handleToolCall` in the MCP server. */
|
|
@@ -140,7 +140,16 @@ async function withCapturedStdout(fn) {
|
|
|
140
140
|
`;
|
|
141
141
|
};
|
|
142
142
|
process.stdout.write = (chunk, encodingOrCallback, callback) => {
|
|
143
|
-
|
|
143
|
+
const text = formatStdoutChunk(chunk, encodingOrCallback);
|
|
144
|
+
if (text.startsWith("{") && text.includes('"jsonrpc":"2.0"')) {
|
|
145
|
+
return originalWrite.call(
|
|
146
|
+
process.stdout,
|
|
147
|
+
chunk,
|
|
148
|
+
encodingOrCallback,
|
|
149
|
+
callback
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
captured += text;
|
|
144
153
|
invokeWriteCallback(encodingOrCallback, callback);
|
|
145
154
|
return true;
|
|
146
155
|
};
|
|
@@ -159,6 +168,32 @@ async function withCapturedStdout(fn) {
|
|
|
159
168
|
}
|
|
160
169
|
}
|
|
161
170
|
}
|
|
171
|
+
function makeProgressSink(raw, minIntervalMs = 1e3) {
|
|
172
|
+
if (!raw) {
|
|
173
|
+
return { send: () => void 0, close: () => void 0 };
|
|
174
|
+
}
|
|
175
|
+
let closed = false;
|
|
176
|
+
let lastSentAt = 0;
|
|
177
|
+
let lastProgress = 0;
|
|
178
|
+
return {
|
|
179
|
+
send: (update) => {
|
|
180
|
+
if (closed) return;
|
|
181
|
+
const now = Date.now();
|
|
182
|
+
if (lastSentAt !== 0 && now - lastSentAt < minIntervalMs) return;
|
|
183
|
+
lastSentAt = now;
|
|
184
|
+
const progress = update.progress !== void 0 && update.progress > lastProgress ? update.progress : lastProgress + 1;
|
|
185
|
+
lastProgress = progress;
|
|
186
|
+
raw({
|
|
187
|
+
progress,
|
|
188
|
+
...update.total !== void 0 ? { total: update.total } : {},
|
|
189
|
+
...update.message ? { message: update.message } : {}
|
|
190
|
+
});
|
|
191
|
+
},
|
|
192
|
+
close: () => {
|
|
193
|
+
closed = true;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
162
197
|
function formatZodError(err) {
|
|
163
198
|
const missing = err.issues.filter((i) => {
|
|
164
199
|
if (i.code !== "invalid_type") return false;
|
|
@@ -193,7 +228,7 @@ function buildMcpDispatcher(manifest) {
|
|
|
193
228
|
byName.set(t.name, t);
|
|
194
229
|
}
|
|
195
230
|
}
|
|
196
|
-
return async (name, args) => {
|
|
231
|
+
return async (name, args, extra) => {
|
|
197
232
|
const tool = byName.get(name);
|
|
198
233
|
if (!tool) {
|
|
199
234
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
|
|
@@ -204,11 +239,13 @@ function buildMcpDispatcher(manifest) {
|
|
|
204
239
|
content: [{ type: "text", text: `${name} failed: ${formatZodError(parsed.error)}` }]
|
|
205
240
|
};
|
|
206
241
|
}
|
|
242
|
+
const sink = makeProgressSink(extra?.onProgress);
|
|
207
243
|
try {
|
|
208
244
|
const result = await withCapturedStdout(
|
|
209
245
|
() => tool.execute(parsed.data, {
|
|
210
246
|
workingDirectory: process.cwd(),
|
|
211
|
-
surface: "mcp"
|
|
247
|
+
surface: "mcp",
|
|
248
|
+
...extra?.onProgress ? { onProgress: sink.send } : {}
|
|
212
249
|
})
|
|
213
250
|
);
|
|
214
251
|
const text = result.success ? JSON.stringify({ success: true, ...result.data }) : result.data ? JSON.stringify({
|
|
@@ -220,10 +257,13 @@ function buildMcpDispatcher(manifest) {
|
|
|
220
257
|
} catch (error) {
|
|
221
258
|
const msg = error instanceof Error ? error.message : String(error);
|
|
222
259
|
return { content: [{ type: "text", text: `${name} threw: ${msg}` }] };
|
|
260
|
+
} finally {
|
|
261
|
+
sink.close();
|
|
223
262
|
}
|
|
224
263
|
};
|
|
225
264
|
}
|
|
226
265
|
export {
|
|
227
266
|
buildMcpDispatcher,
|
|
267
|
+
makeProgressSink,
|
|
228
268
|
manifestToMcpTools
|
|
229
269
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -37,6 +37,18 @@ export interface ExecuteContext {
|
|
|
37
37
|
projectPath: string | null;
|
|
38
38
|
setProjectPath(path: string): void;
|
|
39
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* Optional progress sink. The MCP adapter wires this to MCP
|
|
42
|
+
* `notifications/progress` when the client supplied a progressToken;
|
|
43
|
+
* CLI/Agent surfaces leave it undefined. Omitting `progress` advances an
|
|
44
|
+
* internal monotonic counter; the adapter also throttles and enforces
|
|
45
|
+
* monotonicity defensively.
|
|
46
|
+
*/
|
|
47
|
+
onProgress?: (update: {
|
|
48
|
+
progress?: number;
|
|
49
|
+
total?: number;
|
|
50
|
+
message?: string;
|
|
51
|
+
}) => void;
|
|
40
52
|
}
|
|
41
53
|
export interface ToolExecuteResult {
|
|
42
54
|
success: boolean;
|