@kubb/studio 5.2.4 → 5.2.6
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/index.cjs +49 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.js +51 -9
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs +8 -0
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.ts +584 -2
- package/dist/protocol.js +7 -1
- package/dist/protocol.js.map +1 -1
- package/package.json +4 -4
- package/dist/index-V3fjg2mj.d.ts +0 -569
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __name } from "./rolldown-runtime-CRm0XQPb.js";
|
|
2
|
-
import {
|
|
2
|
+
import { AgentPermissions, ClientInfo, createJobId } from "./protocol.js";
|
|
3
3
|
import { Storage } from "unstorage";
|
|
4
4
|
import { Config, Hookable, KubbHooks } from "@kubb/core";
|
|
5
5
|
//#region src/api.d.ts
|
|
@@ -147,6 +147,11 @@ type StudioConnectedContext = {
|
|
|
147
147
|
agent: string;
|
|
148
148
|
};
|
|
149
149
|
};
|
|
150
|
+
/**
|
|
151
|
+
* Fired once Studio confirms the `agent:connect` handshake was received and the session is fully
|
|
152
|
+
* registered. Distinct from `studio:connected`, which only means the socket is open.
|
|
153
|
+
*/
|
|
154
|
+
type StudioReadyContext = Record<string, never>;
|
|
150
155
|
type StudioDisconnectedContext = {
|
|
151
156
|
/**
|
|
152
157
|
* Why Studio ended the session.
|
|
@@ -187,6 +192,7 @@ declare global {
|
|
|
187
192
|
interface KubbHooksRegistry {
|
|
188
193
|
'studio:connecting': [ctx: StudioConnectingContext];
|
|
189
194
|
'studio:connected': [ctx: StudioConnectedContext];
|
|
195
|
+
'studio:ready': [ctx: StudioReadyContext];
|
|
190
196
|
'studio:disconnected': [ctx: StudioDisconnectedContext];
|
|
191
197
|
'studio:command:start': [ctx: StudioCommandStartContext];
|
|
192
198
|
'studio:command:end': [ctx: StudioCommandEndContext];
|
|
@@ -371,5 +377,5 @@ type PollOptions = {
|
|
|
371
377
|
*/
|
|
372
378
|
export declare function pollForPairingToken({ studioUrl, session, signal }: PollOptions): Promise<PairingResult>;
|
|
373
379
|
//#endregion
|
|
374
|
-
export type
|
|
380
|
+
export { type Client, type ClientOptions, type ConnectionOptions, type StudioConnectedContext, createJobId };
|
|
375
381
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { t as __name } from "./rolldown-runtime-CRm0XQPb.js";
|
|
2
|
-
import { isCommandMessage, isDisconnectMessage, isStudioPingMessage } from "./protocol.js";
|
|
2
|
+
import { createJobId, isCommandMessage, isDisconnectMessage, isStudioPingMessage, isStudioReadyMessage } from "./protocol.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { styleText } from "node:util";
|
|
5
5
|
import { readFile, writeFile } from "node:fs/promises";
|
|
6
|
-
import path from "node:path";
|
|
6
|
+
import path, { isAbsolute, relative, resolve } from "node:path";
|
|
7
7
|
import { spawn } from "node:child_process";
|
|
8
8
|
import { FetchError, ofetch } from "ofetch";
|
|
9
9
|
import { hash, randomBytes } from "node:crypto";
|
|
@@ -559,12 +559,14 @@ async function disconnect({ sessionId, token, studioUrl, slug }) {
|
|
|
559
559
|
});
|
|
560
560
|
console.log(styleText("green", `[${tag}] Disconnected from Studio`));
|
|
561
561
|
} catch (error) {
|
|
562
|
+
const statusCode = error?.statusCode;
|
|
563
|
+
if (statusCode !== void 0 && statusCode >= 400 && statusCode < 500) return;
|
|
562
564
|
console.warn(styleText("yellow", `[${tag}] Failed to notify Studio of disconnection: ${getErrorMessage(error)}`));
|
|
563
565
|
}
|
|
564
566
|
}
|
|
565
567
|
//#endregion
|
|
566
568
|
//#region package.json
|
|
567
|
-
var version = "5.2.
|
|
569
|
+
var version = "5.2.6";
|
|
568
570
|
//#endregion
|
|
569
571
|
//#region src/hooks.ts
|
|
570
572
|
/**
|
|
@@ -1517,6 +1519,9 @@ const CONNECT_TIMEOUT_MS = 5e3;
|
|
|
1517
1519
|
*/
|
|
1518
1520
|
const eventSeqCounters = /* @__PURE__ */ new WeakMap();
|
|
1519
1521
|
const require = createRequire(import.meta.url);
|
|
1522
|
+
function relativeStoragePath(root, filePath) {
|
|
1523
|
+
return (isAbsolute(filePath) ? relative(resolve(root), filePath) : filePath).replaceAll("\\", "/");
|
|
1524
|
+
}
|
|
1520
1525
|
async function resolvePeerDependencies(names) {
|
|
1521
1526
|
const uniqueNames = [...new Set(names.map(toPackageName))];
|
|
1522
1527
|
const peerDependencies = {};
|
|
@@ -1571,9 +1576,10 @@ function sendAgentMessage(ws, message) {
|
|
|
1571
1576
|
* Sends a single `kubb:error` payload to Studio, stamped from the same per-socket counter the event stream
|
|
1572
1577
|
* uses so Studio can still order it against the generation events around it.
|
|
1573
1578
|
*/
|
|
1574
|
-
function sendErrorMessage(ws, error) {
|
|
1579
|
+
function sendErrorMessage(ws, error, jobId) {
|
|
1575
1580
|
sendAgentMessage(ws, {
|
|
1576
1581
|
type: "agent:data",
|
|
1582
|
+
jobId,
|
|
1577
1583
|
payload: {
|
|
1578
1584
|
type: "kubb:error",
|
|
1579
1585
|
data: [{
|
|
@@ -1588,7 +1594,7 @@ function sendErrorMessage(ws, error) {
|
|
|
1588
1594
|
/**
|
|
1589
1595
|
* Forwards selected Kubb lifecycle events to Studio as data messages for the active session.
|
|
1590
1596
|
*/
|
|
1591
|
-
function setupEventsStream(ws, hooks) {
|
|
1597
|
+
function setupEventsStream(ws, hooks, jobId) {
|
|
1592
1598
|
const unhooks = [];
|
|
1593
1599
|
/**
|
|
1594
1600
|
* Registers a listener and keeps its remover, so one generation's listeners come off the session
|
|
@@ -1600,6 +1606,7 @@ function setupEventsStream(ws, hooks) {
|
|
|
1600
1606
|
function sendDataMessage(payload) {
|
|
1601
1607
|
sendAgentMessage(ws, {
|
|
1602
1608
|
type: "agent:data",
|
|
1609
|
+
jobId,
|
|
1603
1610
|
payload: {
|
|
1604
1611
|
...payload,
|
|
1605
1612
|
timestamp: Date.now(),
|
|
@@ -1698,7 +1705,7 @@ function setupEventsStream(ws, hooks) {
|
|
|
1698
1705
|
limit: FILE_READ_CONCURRENCY,
|
|
1699
1706
|
run: async (path) => {
|
|
1700
1707
|
const content = await storage.readItem(path);
|
|
1701
|
-
if (content !== null) files[path] = content;
|
|
1708
|
+
if (content !== null) files[relativeStoragePath(config.root, path)] = content;
|
|
1702
1709
|
}
|
|
1703
1710
|
});
|
|
1704
1711
|
sendDataMessage({
|
|
@@ -1786,6 +1793,12 @@ function setupEventsStream(ws, hooks) {
|
|
|
1786
1793
|
//#endregion
|
|
1787
1794
|
//#region src/StudioSession.ts
|
|
1788
1795
|
/**
|
|
1796
|
+
* How long the `agent:connect` handshake may go unacknowledged before `studio:ready` is given up
|
|
1797
|
+
* on for this open. A Studio that predates the ack never sends one, so this only ever produces a
|
|
1798
|
+
* warning, not a reconnect.
|
|
1799
|
+
*/
|
|
1800
|
+
const READY_TIMEOUT_MS = 1e4;
|
|
1801
|
+
/**
|
|
1789
1802
|
* Fills in a host's options: the hosted Studio URL, the current working directory, and every
|
|
1790
1803
|
* permission off unless granted. Idempotent, so a reconnect can pass an already-resolved bag
|
|
1791
1804
|
* back in.
|
|
@@ -1853,9 +1866,11 @@ var StudioSession = class {
|
|
|
1853
1866
|
#session;
|
|
1854
1867
|
#ws;
|
|
1855
1868
|
#studioVersion;
|
|
1869
|
+
#activeJobId = null;
|
|
1856
1870
|
#disposed = false;
|
|
1857
1871
|
#isGenerating = false;
|
|
1858
1872
|
#heartbeatTimer;
|
|
1873
|
+
#readyTimer;
|
|
1859
1874
|
#lastPongAt = Date.now();
|
|
1860
1875
|
constructor(options) {
|
|
1861
1876
|
this.#options = applyStudioDefaults(options);
|
|
@@ -1899,7 +1914,7 @@ var StudioSession = class {
|
|
|
1899
1914
|
signal?.addEventListener("abort", this.#onAbort, { once: true });
|
|
1900
1915
|
this.#unhooks.push(() => signal?.removeEventListener("abort", this.#onAbort));
|
|
1901
1916
|
this.#heartbeatTimer = setInterval(() => this.#sendHeartbeat(), heartbeatInterval);
|
|
1902
|
-
this.#unhooks.push(this.#hooks.hook("kubb:error", ({ error }) => sendErrorMessage(ws, error)));
|
|
1917
|
+
this.#unhooks.push(this.#hooks.hook("kubb:error", ({ error }) => sendErrorMessage(ws, error, this.#activeJobId ?? "connection")));
|
|
1903
1918
|
} catch (error) {
|
|
1904
1919
|
await this.#hooks.callHook("studio:error", { error: toError(error) });
|
|
1905
1920
|
if (error instanceof InvalidAgentTokenError) throw error;
|
|
@@ -1989,10 +2004,24 @@ var StudioSession = class {
|
|
|
1989
2004
|
});
|
|
1990
2005
|
try {
|
|
1991
2006
|
await this.#sendConnectedPayload();
|
|
2007
|
+
this.#waitForReady();
|
|
1992
2008
|
} catch (error) {
|
|
1993
2009
|
await this.#warn(`Failed to send the connect payload: ${getErrorMessage(error)}`);
|
|
1994
2010
|
}
|
|
1995
2011
|
}
|
|
2012
|
+
/**
|
|
2013
|
+
* Arms the timeout for Studio's `studio:ready` acknowledgement. Re-armed on every open, since a
|
|
2014
|
+
* command sent while the agent is not attached is lost either way (see `#sendConnectedPayload`),
|
|
2015
|
+
* so each reconnect needs its own fresh wait.
|
|
2016
|
+
*/
|
|
2017
|
+
#waitForReady() {
|
|
2018
|
+
if (this.#disposed) return;
|
|
2019
|
+
clearTimeout(this.#readyTimer);
|
|
2020
|
+
this.#readyTimer = setTimeout(() => {
|
|
2021
|
+
this.#readyTimer = void 0;
|
|
2022
|
+
Promise.resolve(this.#warn(`Kubb Studio did not confirm the connection was ready within ${READY_TIMEOUT_MS}ms`)).catch(() => {});
|
|
2023
|
+
}, READY_TIMEOUT_MS);
|
|
2024
|
+
}
|
|
1996
2025
|
#onOpen = () => void this.#handleOpen().catch(() => {});
|
|
1997
2026
|
#onAbort = () => void this.#end({
|
|
1998
2027
|
reason: "shutdown",
|
|
@@ -2012,6 +2041,8 @@ var StudioSession = class {
|
|
|
2012
2041
|
dispose(reason = "cleanup") {
|
|
2013
2042
|
clearInterval(this.#heartbeatTimer);
|
|
2014
2043
|
this.#heartbeatTimer = void 0;
|
|
2044
|
+
clearTimeout(this.#readyTimer);
|
|
2045
|
+
this.#readyTimer = void 0;
|
|
2015
2046
|
try {
|
|
2016
2047
|
this.#ws?.close(1e3, reason);
|
|
2017
2048
|
} catch {}
|
|
@@ -2046,6 +2077,12 @@ var StudioSession = class {
|
|
|
2046
2077
|
this.#lastPongAt = Date.now();
|
|
2047
2078
|
return;
|
|
2048
2079
|
}
|
|
2080
|
+
if (isStudioReadyMessage(data)) {
|
|
2081
|
+
clearTimeout(this.#readyTimer);
|
|
2082
|
+
this.#readyTimer = void 0;
|
|
2083
|
+
await this.#hooks.callHook("studio:ready", {});
|
|
2084
|
+
return;
|
|
2085
|
+
}
|
|
2049
2086
|
if (isDisconnectMessage(data)) {
|
|
2050
2087
|
await this.#handleDisconnect(data.reason);
|
|
2051
2088
|
return;
|
|
@@ -2098,6 +2135,7 @@ var StudioSession = class {
|
|
|
2098
2135
|
return;
|
|
2099
2136
|
}
|
|
2100
2137
|
this.#isGenerating = true;
|
|
2138
|
+
this.#activeJobId = data.jobId;
|
|
2101
2139
|
try {
|
|
2102
2140
|
const config = await loadConfig();
|
|
2103
2141
|
const patch = data.payload;
|
|
@@ -2110,7 +2148,7 @@ var StudioSession = class {
|
|
|
2110
2148
|
await this.#warn(`Ignored the spec from Studio; set ${remedy} to generate from it`);
|
|
2111
2149
|
}
|
|
2112
2150
|
const resolvedPlugins = plugins ?? config.plugins;
|
|
2113
|
-
const detach = [setupHookListener(this.#hooks, root), setupEventsStream(ws, this.#hooks)];
|
|
2151
|
+
const detach = [setupHookListener(this.#hooks, root), setupEventsStream(ws, this.#hooks, data.jobId)];
|
|
2114
2152
|
try {
|
|
2115
2153
|
await generate({
|
|
2116
2154
|
config: {
|
|
@@ -2138,6 +2176,7 @@ var StudioSession = class {
|
|
|
2138
2176
|
});
|
|
2139
2177
|
} finally {
|
|
2140
2178
|
this.#isGenerating = false;
|
|
2179
|
+
this.#activeJobId = null;
|
|
2141
2180
|
}
|
|
2142
2181
|
}
|
|
2143
2182
|
async #handleSave(ws, data, command) {
|
|
@@ -2146,6 +2185,7 @@ var StudioSession = class {
|
|
|
2146
2185
|
await this.#warn("Ignored save: the message carried no edits");
|
|
2147
2186
|
sendAgentMessage(ws, {
|
|
2148
2187
|
type: "agent:save",
|
|
2188
|
+
jobId: data.jobId,
|
|
2149
2189
|
payload: {
|
|
2150
2190
|
outcomes: [],
|
|
2151
2191
|
changed: false
|
|
@@ -2156,6 +2196,7 @@ var StudioSession = class {
|
|
|
2156
2196
|
const edits = data.edits;
|
|
2157
2197
|
const refuse = (reason) => sendAgentMessage(ws, {
|
|
2158
2198
|
type: "agent:save",
|
|
2199
|
+
jobId: data.jobId,
|
|
2159
2200
|
payload: {
|
|
2160
2201
|
outcomes: edits.map((edit) => ({
|
|
2161
2202
|
edit,
|
|
@@ -2179,6 +2220,7 @@ var StudioSession = class {
|
|
|
2179
2220
|
if (changed) await writeFile(configFile, patched, "utf-8");
|
|
2180
2221
|
sendAgentMessage(ws, {
|
|
2181
2222
|
type: "agent:save",
|
|
2223
|
+
jobId: data.jobId,
|
|
2182
2224
|
payload: {
|
|
2183
2225
|
outcomes,
|
|
2184
2226
|
changed,
|
|
@@ -2400,6 +2442,6 @@ async function pollForPairingToken({ studioUrl = agentDefaults.studioUrl, sessio
|
|
|
2400
2442
|
throw new Error("The pairing code expired, pair again");
|
|
2401
2443
|
}
|
|
2402
2444
|
//#endregion
|
|
2403
|
-
export { InvalidAgentTokenError, PairingCanceledError, createClient, createFileStorage, defaultStudioUrl, pollForPairingToken, runConnection, setStorage, startPairing };
|
|
2445
|
+
export { InvalidAgentTokenError, PairingCanceledError, createClient, createFileStorage, createJobId, defaultStudioUrl, pollForPairingToken, runConnection, setStorage, startPairing };
|
|
2404
2446
|
|
|
2405
2447
|
//# sourceMappingURL=index.js.map
|