@skaile/workspaces 1.4.0 → 1.6.0
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/CHANGELOG.md +326 -0
- package/dist/app-sdk/index.js +241 -55
- package/dist/app-sdk/index.js.map +1 -1
- package/dist/app-sdk/src/app.d.ts +55 -4
- package/dist/app-sdk/src/app.d.ts.map +1 -1
- package/dist/app-sdk/src/connection-params.d.ts +117 -0
- package/dist/app-sdk/src/connection-params.d.ts.map +1 -0
- package/dist/app-sdk/src/index.d.ts +3 -2
- package/dist/app-sdk/src/index.d.ts.map +1 -1
- package/dist/app-sdk/src/transport.d.ts +9 -0
- package/dist/app-sdk/src/transport.d.ts.map +1 -1
- package/dist/app-sdk/src/ws-transport.d.ts +18 -2
- package/dist/app-sdk/src/ws-transport.d.ts.map +1 -1
- package/dist/{asset-feeds-NG5TKB3V.js → asset-feeds-T35ELX2O.js} +9 -9
- package/dist/{asset-feeds-NG5TKB3V.js.map → asset-feeds-T35ELX2O.js.map} +1 -1
- package/dist/asset-manager/index.js +11 -6
- package/dist/asset-manager/installer.js +5 -5
- package/dist/asset-manager/src/index.d.ts.map +1 -1
- package/dist/{chunk-BE5KTS2L.js → chunk-2DWXGUZA.js} +2 -2
- package/dist/{chunk-BE5KTS2L.js.map → chunk-2DWXGUZA.js.map} +1 -1
- package/dist/{chunk-XBG2YDQQ.js → chunk-2ZMO4CFG.js} +3 -3
- package/dist/{chunk-XBG2YDQQ.js.map → chunk-2ZMO4CFG.js.map} +1 -1
- package/dist/{chunk-X57AMTAH.js → chunk-3CHARYD4.js} +16 -7
- package/dist/chunk-3CHARYD4.js.map +1 -0
- package/dist/{chunk-LCTRCWBK.js → chunk-6NR2BEIY.js} +4 -4
- package/dist/{chunk-LCTRCWBK.js.map → chunk-6NR2BEIY.js.map} +1 -1
- package/dist/{chunk-JZPEEMIM.js → chunk-BEO6KN7P.js} +5 -5
- package/dist/{chunk-JZPEEMIM.js.map → chunk-BEO6KN7P.js.map} +1 -1
- package/dist/{chunk-AAGD2J4H.js → chunk-CBA2XIBF.js} +33 -15
- package/dist/chunk-CBA2XIBF.js.map +1 -0
- package/dist/{chunk-GWDZW44L.js → chunk-DMG6NJH7.js} +4 -4
- package/dist/{chunk-GWDZW44L.js.map → chunk-DMG6NJH7.js.map} +1 -1
- package/dist/{chunk-HWTQ6HPO.js → chunk-FDE7XRQA.js} +56 -8
- package/dist/chunk-FDE7XRQA.js.map +1 -0
- package/dist/{chunk-26YCXWCU.js → chunk-HY33RMEA.js} +3 -3
- package/dist/{chunk-26YCXWCU.js.map → chunk-HY33RMEA.js.map} +1 -1
- package/dist/chunk-J5TI55E7.js +203 -0
- package/dist/chunk-J5TI55E7.js.map +1 -0
- package/dist/chunk-JIDKX7GC.js +122 -0
- package/dist/chunk-JIDKX7GC.js.map +1 -0
- package/dist/{chunk-BDUANIAR.js → chunk-KEQ4GLVM.js} +3 -3
- package/dist/{chunk-BDUANIAR.js.map → chunk-KEQ4GLVM.js.map} +1 -1
- package/dist/{chunk-DNFIKRO6.js → chunk-KMY7HVQ6.js} +3 -3
- package/dist/{chunk-DNFIKRO6.js.map → chunk-KMY7HVQ6.js.map} +1 -1
- package/dist/{chunk-B6SSXVF2.js → chunk-MZV6WCF3.js} +81 -49
- package/dist/chunk-MZV6WCF3.js.map +1 -0
- package/dist/{chunk-DZCK7GRW.js → chunk-P4VHQA4N.js} +22 -6
- package/dist/chunk-P4VHQA4N.js.map +1 -0
- package/dist/{chunk-OSCJRUKM.js → chunk-RDYOU6N6.js} +2 -2
- package/dist/{chunk-OSCJRUKM.js.map → chunk-RDYOU6N6.js.map} +1 -1
- package/dist/{chunk-77OXU44H.js → chunk-RSYV7XEN.js} +13 -7
- package/dist/chunk-RSYV7XEN.js.map +1 -0
- package/dist/{chunk-OUWNTFDG.js → chunk-UQKYT4V6.js} +38 -6
- package/dist/chunk-UQKYT4V6.js.map +1 -0
- package/dist/chunk-WYGXYGJH.js +688 -0
- package/dist/chunk-WYGXYGJH.js.map +1 -0
- package/dist/{chunk-TJRWGMD2.js → chunk-ZBZQC5R7.js} +3 -3
- package/dist/{chunk-TJRWGMD2.js.map → chunk-ZBZQC5R7.js.map} +1 -1
- package/dist/cli/index.js +259 -192
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/src/commands/catalog.d.ts +18 -0
- package/dist/cli/src/commands/catalog.d.ts.map +1 -1
- package/dist/cli/src/commands/library-cmd.d.ts +2 -2
- package/dist/cli/src/commands/library-cmd.d.ts.map +1 -1
- package/dist/cli/src/commands/library-publish.d.ts +53 -0
- package/dist/cli/src/commands/library-publish.d.ts.map +1 -0
- package/dist/cli/src/commands/preset-cmd.d.ts +6 -0
- package/dist/cli/src/commands/preset-cmd.d.ts.map +1 -1
- package/dist/cli/src/commands/preset-export.d.ts +10 -1
- package/dist/cli/src/commands/preset-export.d.ts.map +1 -1
- package/dist/cli/src/commands/project.d.ts.map +1 -1
- package/dist/cli/src/commands/publish-core.d.ts +75 -0
- package/dist/cli/src/commands/publish-core.d.ts.map +1 -0
- package/dist/cli/src/commands/run.d.ts.map +1 -1
- package/dist/cli/src/commands/source-manifest-publish.d.ts +82 -0
- package/dist/cli/src/commands/source-manifest-publish.d.ts.map +1 -0
- package/dist/cli/src/commands/source-manifest.d.ts +65 -6
- package/dist/cli/src/commands/source-manifest.d.ts.map +1 -1
- package/dist/cli/src/commands/source.d.ts +13 -0
- package/dist/cli/src/commands/source.d.ts.map +1 -1
- package/dist/cli/src/commands/store.d.ts.map +1 -1
- package/dist/cli/src/driver-error.d.ts +12 -0
- package/dist/cli/src/driver-error.d.ts.map +1 -0
- package/dist/cli/src/open-library.d.ts +4 -0
- package/dist/cli/src/open-library.d.ts.map +1 -1
- package/dist/cli/src/store-auth.d.ts +42 -0
- package/dist/cli/src/store-auth.d.ts.map +1 -0
- package/dist/cli/src/store-client.d.ts +20 -22
- package/dist/cli/src/store-client.d.ts.map +1 -1
- package/dist/cli/src/store-publisher.d.ts +91 -0
- package/dist/cli/src/store-publisher.d.ts.map +1 -0
- package/dist/connectors/config.js +5 -5
- package/dist/connectors/index.js +6 -6
- package/dist/core/index.js +4 -4
- package/dist/core/publish-manifest.js +1 -1
- package/dist/core/runtime-assets.js +3 -3
- package/dist/core/src/cache-paths.d.ts +17 -0
- package/dist/core/src/cache-paths.d.ts.map +1 -1
- package/dist/core/src/index.d.ts +2 -2
- package/dist/core/src/index.d.ts.map +1 -1
- package/dist/core/src/publish-manifest.d.ts.map +1 -1
- package/dist/core/src/repo-manager.d.ts +13 -2
- package/dist/core/src/repo-manager.d.ts.map +1 -1
- package/dist/core/src/walker.d.ts +10 -0
- package/dist/core/src/walker.d.ts.map +1 -1
- package/dist/core/workspace-config.js +2 -2
- package/dist/deploy/index.js +4 -4
- package/dist/discovery/index.js +2 -2
- package/dist/discovery/src/discover-manifest.d.ts.map +1 -1
- package/dist/discovery/src/discover.d.ts +17 -4
- package/dist/discovery/src/discover.d.ts.map +1 -1
- package/dist/discovery/src/index.d.ts +1 -1
- package/dist/discovery/src/index.d.ts.map +1 -1
- package/dist/discovery/src/reference-lint.d.ts +7 -0
- package/dist/discovery/src/reference-lint.d.ts.map +1 -1
- package/dist/discovery/src/source-config.d.ts +28 -0
- package/dist/discovery/src/source-config.d.ts.map +1 -1
- package/dist/discovery/src/tree-entries.d.ts.map +1 -1
- package/dist/{ensure-sources-CHC7X7AJ.js → ensure-sources-QDVFUIUP.js} +14 -9
- package/dist/ensure-sources-QDVFUIUP.js.map +1 -0
- package/dist/factory-assets/connectors/box.js +6 -6
- package/dist/factory-assets/connectors/deploy.js +6 -6
- package/dist/factory-assets/connectors/devserver.js +6 -6
- package/dist/factory-assets/connectors/flow/adapter.js +6 -6
- package/dist/factory-assets/connectors/flow/engine.js +4 -4
- package/dist/factory-assets/connectors/flow/run-flow.js +7 -7
- package/dist/factory-assets/connectors/flow.js +6 -6
- package/dist/factory-assets/connectors/git.js +6 -6
- package/dist/factory-assets/connectors/gmail.js +6 -6
- package/dist/factory-assets/connectors/googledrive.js +6 -6
- package/dist/factory-assets/connectors/local.js +6 -6
- package/dist/factory-assets/connectors/mattermost.js +6 -6
- package/dist/factory-assets/connectors/memory.js +6 -6
- package/dist/factory-assets/connectors/minio.js +6 -6
- package/dist/factory-assets/connectors/postgres.js +6 -6
- package/dist/factory-assets/connectors/s3.js +6 -6
- package/dist/factory-assets/connectors/sharepoint.js +6 -6
- package/dist/factory-assets/connectors/sqlite.js +6 -6
- package/dist/factory-assets/connectors/static-server.js +6 -6
- package/dist/factory-assets/connectors/tunnel.js +6 -6
- package/dist/factory-assets/connectors/webdav.js +6 -6
- package/dist/factory-assets/connectors/xstate-store.js +6 -6
- package/dist/factory-assets/connectors/xstate.js +6 -6
- package/dist/{flows-CGOCUNO6.js → flows-MRVXTYWC.js} +3 -3
- package/dist/{flows-CGOCUNO6.js.map → flows-MRVXTYWC.js.map} +1 -1
- package/dist/{helpers-GWG4CYYC.js → helpers-TF7GCBRQ.js} +14 -9
- package/dist/{helpers-GWG4CYYC.js.map → helpers-TF7GCBRQ.js.map} +1 -1
- package/dist/library/index.js +6 -6
- package/dist/library/install.js +4 -4
- package/dist/library/src/index.d.ts +2 -2
- package/dist/library/src/index.d.ts.map +1 -1
- package/dist/library/src/local/local-catalog-source.d.ts +12 -1
- package/dist/library/src/local/local-catalog-source.d.ts.map +1 -1
- package/dist/library/src/sync/manifest-writeback.d.ts +36 -0
- package/dist/library/src/sync/manifest-writeback.d.ts.map +1 -1
- package/dist/library-publish-K5IBE44D.js +249 -0
- package/dist/library-publish-K5IBE44D.js.map +1 -0
- package/dist/{open-library-37PXN6MK.js → open-library-4HMIDK3N.js} +13 -13
- package/dist/{open-library-37PXN6MK.js.map → open-library-4HMIDK3N.js.map} +1 -1
- package/dist/{paths-UVOHWUPY.js → paths-P3DWFUPS.js} +7 -7
- package/dist/{paths-UVOHWUPY.js.map → paths-P3DWFUPS.js.map} +1 -1
- package/dist/{plugin-store-EIMVEH3O.js → plugin-store-S7OMGI7N.js} +6 -6
- package/dist/{plugin-store-EIMVEH3O.js.map → plugin-store-S7OMGI7N.js.map} +1 -1
- package/dist/runner/index.js +8 -8
- package/dist/runner/src/agent.d.ts.map +1 -1
- package/dist/runner/src/capability-registry.d.ts +21 -2
- package/dist/runner/src/capability-registry.d.ts.map +1 -1
- package/dist/sdk/asset-manager.js +11 -6
- package/dist/sdk/core.js +4 -4
- package/dist/sdk/flow.js +4 -4
- package/dist/sdk/index.js +8 -8
- package/dist/sdk/runner.js +8 -8
- package/dist/{setup-DINVR6ZQ.js → setup-2CCS63L6.js} +6 -6
- package/dist/{setup-DINVR6ZQ.js.map → setup-2CCS63L6.js.map} +1 -1
- package/dist/source-LOAM7IIQ.js +30 -0
- package/dist/{source-RXAHIGMD.js.map → source-LOAM7IIQ.js.map} +1 -1
- package/dist/source-manifest-publish-TGVZ6KMC.js +228 -0
- package/dist/source-manifest-publish-TGVZ6KMC.js.map +1 -0
- package/dist/tui/index.js +8 -8
- package/dist/workspace-plugin/adapters/mcp.js +1 -1
- package/dist/workspace-plugin/adapters/omp.js +3 -3
- package/dist/workspace-plugin/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-77OXU44H.js.map +0 -1
- package/dist/chunk-AAGD2J4H.js.map +0 -1
- package/dist/chunk-B6SSXVF2.js.map +0 -1
- package/dist/chunk-BJHZHNKR.js +0 -93
- package/dist/chunk-BJHZHNKR.js.map +0 -1
- package/dist/chunk-DZCK7GRW.js.map +0 -1
- package/dist/chunk-HWTQ6HPO.js.map +0 -1
- package/dist/chunk-OUWNTFDG.js.map +0 -1
- package/dist/chunk-QM2TT2PJ.js +0 -385
- package/dist/chunk-QM2TT2PJ.js.map +0 -1
- package/dist/chunk-X57AMTAH.js.map +0 -1
- package/dist/ensure-sources-CHC7X7AJ.js.map +0 -1
- package/dist/source-RXAHIGMD.js +0 -30
- package/dist/store-client-T3F663YG.js +0 -15
- package/dist/store-client-T3F663YG.js.map +0 -1
package/dist/app-sdk/index.js
CHANGED
|
@@ -30,6 +30,20 @@ var consoleLogger = {
|
|
|
30
30
|
|
|
31
31
|
// app-sdk/src/app.ts
|
|
32
32
|
var DEFAULT_HELLO_TIMEOUT_MS = 1e4;
|
|
33
|
+
var DEFAULT_RECONNECT = {
|
|
34
|
+
maxAttempts: 5,
|
|
35
|
+
baseMs: 1e3,
|
|
36
|
+
maxMs: 3e4
|
|
37
|
+
};
|
|
38
|
+
var AppHandshakeRefusedError = class extends Error {
|
|
39
|
+
retryable;
|
|
40
|
+
constructor(reason, retryable) {
|
|
41
|
+
super(`app handshake refused: ${reason}`);
|
|
42
|
+
this.name = "AppHandshakeRefusedError";
|
|
43
|
+
this.retryable = retryable;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
33
47
|
function createSkaileApp(options) {
|
|
34
48
|
const {
|
|
35
49
|
appId,
|
|
@@ -38,11 +52,23 @@ function createSkaileApp(options) {
|
|
|
38
52
|
helloTimeoutMs = DEFAULT_HELLO_TIMEOUT_MS,
|
|
39
53
|
logger = noopLogger
|
|
40
54
|
} = options;
|
|
55
|
+
const reconnectEnabled = options.reconnect !== false;
|
|
56
|
+
const policy = {
|
|
57
|
+
...DEFAULT_RECONNECT,
|
|
58
|
+
...options.reconnect === false ? {} : options.reconnect
|
|
59
|
+
};
|
|
41
60
|
const registry = /* @__PURE__ */ new Map();
|
|
61
|
+
const sessionListeners = /* @__PURE__ */ new Set();
|
|
42
62
|
let session = null;
|
|
63
|
+
let closed = false;
|
|
64
|
+
let connecting = false;
|
|
65
|
+
let subscribed = false;
|
|
66
|
+
let epoch = 0;
|
|
43
67
|
let unsubscribeMessage = null;
|
|
44
68
|
let unsubscribeDisconnect = null;
|
|
45
|
-
let
|
|
69
|
+
let reconnectTimer;
|
|
70
|
+
let reconnectWake = null;
|
|
71
|
+
let pending = null;
|
|
46
72
|
function toWire(cap) {
|
|
47
73
|
return {
|
|
48
74
|
name: cap.name,
|
|
@@ -61,6 +87,10 @@ function createSkaileApp(options) {
|
|
|
61
87
|
render: cap.render
|
|
62
88
|
};
|
|
63
89
|
}
|
|
90
|
+
function setSession(next) {
|
|
91
|
+
session = next;
|
|
92
|
+
for (const h of sessionListeners) h(next);
|
|
93
|
+
}
|
|
64
94
|
function registerCapability(cap) {
|
|
65
95
|
registry.set(cap.name, cap);
|
|
66
96
|
if (session) {
|
|
@@ -96,69 +126,146 @@ function createSkaileApp(options) {
|
|
|
96
126
|
transport.send({ type: "capability_result", callId, result: { error: message } });
|
|
97
127
|
}
|
|
98
128
|
}
|
|
99
|
-
function
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
129
|
+
function onMessage(message) {
|
|
130
|
+
switch (message.type) {
|
|
131
|
+
case "app_hello_ack": {
|
|
132
|
+
const bound = { sessionId: message.sessionId, userId: message.userId };
|
|
133
|
+
pending?.settle(null, bound);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
case "app_hello_nack":
|
|
137
|
+
pending?.settle(new AppHandshakeRefusedError(message.error, message.retryable === true));
|
|
138
|
+
return;
|
|
139
|
+
case "capability_invoked":
|
|
140
|
+
void handleInvocation(message.callId, message.name, message.input);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function onDisconnect() {
|
|
145
|
+
if (pending) {
|
|
146
|
+
pending.settle(new Error("transport disconnected during handshake"));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (closed || !session) return;
|
|
150
|
+
logger.warn("app connection lost", { appId });
|
|
151
|
+
setSession(null);
|
|
152
|
+
if (!reconnectEnabled) return;
|
|
153
|
+
connecting = true;
|
|
154
|
+
const loopEpoch = epoch;
|
|
155
|
+
void reconnectLoop().finally(() => {
|
|
156
|
+
if (epoch === loopEpoch) connecting = false;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
function ensureSubscribed() {
|
|
160
|
+
if (subscribed) return;
|
|
161
|
+
unsubscribeMessage = transport.onMessage(onMessage);
|
|
162
|
+
unsubscribeDisconnect = transport.onDisconnect(onDisconnect);
|
|
163
|
+
subscribed = true;
|
|
164
|
+
}
|
|
165
|
+
function performHandshake() {
|
|
166
|
+
ensureSubscribed();
|
|
167
|
+
return transport.connect().then(
|
|
168
|
+
() => new Promise((resolve, reject) => {
|
|
169
|
+
if (closed) {
|
|
170
|
+
reject(new Error("app closed during handshake"));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
let settled = false;
|
|
174
|
+
const timer = setTimeout(() => {
|
|
175
|
+
pending?.settle(new Error(`app handshake timed out after ${helloTimeoutMs}ms`));
|
|
176
|
+
}, helloTimeoutMs);
|
|
177
|
+
pending = {
|
|
178
|
+
settle: (err, bound) => {
|
|
119
179
|
if (settled) return;
|
|
120
180
|
settled = true;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
181
|
+
clearTimeout(timer);
|
|
182
|
+
pending = null;
|
|
183
|
+
if (err || !bound) {
|
|
184
|
+
reject(err ?? new Error("app handshake failed"));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
setSession(bound);
|
|
188
|
+
logger.info("app connection established", { appId, sessionId: bound.sessionId });
|
|
124
189
|
const wire = Array.from(registry.values()).map(toWire);
|
|
125
190
|
if (wire.length > 0) {
|
|
126
191
|
transport.send({ type: "capability_register", capabilities: wire });
|
|
127
192
|
}
|
|
128
|
-
resolve(
|
|
129
|
-
return;
|
|
193
|
+
resolve(bound);
|
|
130
194
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
195
|
+
};
|
|
196
|
+
transport.send({ type: "app_hello", appId, protocolVersion });
|
|
197
|
+
}),
|
|
198
|
+
(err) => Promise.reject(err instanceof Error ? err : new Error(String(err)))
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
const backoffMs = (attempt) => Math.min(policy.maxMs, policy.baseMs * 2 ** Math.max(0, attempt - 1));
|
|
202
|
+
async function reconnectLoop() {
|
|
203
|
+
const myEpoch = epoch;
|
|
204
|
+
for (let attempt = 1; attempt <= policy.maxAttempts; attempt++) {
|
|
205
|
+
const wait = backoffMs(attempt);
|
|
206
|
+
logger.debug("scheduling app reconnect", { appId, attempt, waitMs: wait });
|
|
207
|
+
await new Promise((r) => {
|
|
208
|
+
reconnectWake = r;
|
|
209
|
+
reconnectTimer = setTimeout(r, wait);
|
|
210
|
+
});
|
|
211
|
+
reconnectWake = null;
|
|
212
|
+
if (closed || epoch !== myEpoch) {
|
|
213
|
+
logger.debug("app reconnect cancelled", { appId, attempt });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
await performHandshake();
|
|
218
|
+
logger.info("app connection re-established", { appId });
|
|
219
|
+
return;
|
|
220
|
+
} catch (err) {
|
|
221
|
+
if (err instanceof AppHandshakeRefusedError && !err.retryable) {
|
|
222
|
+
logger.error("app reconnect refused permanently", { appId, error: err.message });
|
|
223
|
+
return;
|
|
137
224
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
)
|
|
154
|
-
|
|
225
|
+
logger.warn("app reconnect attempt failed", {
|
|
226
|
+
appId,
|
|
227
|
+
attempt,
|
|
228
|
+
error: err instanceof Error ? err.message : String(err)
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
logger.error("app reconnect gave up", { appId, attempts: policy.maxAttempts });
|
|
233
|
+
}
|
|
234
|
+
async function connect() {
|
|
235
|
+
if (session) return session;
|
|
236
|
+
if (connecting) throw new Error("app connect() is already in progress");
|
|
237
|
+
connecting = true;
|
|
238
|
+
closed = false;
|
|
239
|
+
try {
|
|
240
|
+
for (let attempt = 1; ; attempt++) {
|
|
241
|
+
try {
|
|
242
|
+
return await performHandshake();
|
|
243
|
+
} catch (err) {
|
|
244
|
+
const transient = err instanceof AppHandshakeRefusedError && err.retryable;
|
|
245
|
+
if (!transient || !reconnectEnabled || attempt >= policy.maxAttempts || closed) {
|
|
246
|
+
throw err;
|
|
247
|
+
}
|
|
248
|
+
logger.debug("app handshake refused (retryable), retrying", { appId, attempt });
|
|
249
|
+
await delay(backoffMs(attempt));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
} finally {
|
|
253
|
+
connecting = false;
|
|
254
|
+
}
|
|
155
255
|
}
|
|
156
256
|
async function close() {
|
|
157
|
-
|
|
257
|
+
closed = true;
|
|
258
|
+
epoch++;
|
|
259
|
+
clearTimeout(reconnectTimer);
|
|
260
|
+
reconnectWake?.();
|
|
261
|
+
reconnectWake = null;
|
|
262
|
+
connecting = false;
|
|
263
|
+
pending?.settle(new Error("app closed during handshake"));
|
|
158
264
|
unsubscribeMessage?.();
|
|
159
265
|
unsubscribeDisconnect?.();
|
|
160
266
|
unsubscribeMessage = null;
|
|
161
267
|
unsubscribeDisconnect = null;
|
|
268
|
+
subscribed = false;
|
|
162
269
|
session = null;
|
|
163
270
|
await transport.disconnect();
|
|
164
271
|
}
|
|
@@ -172,9 +279,84 @@ function createSkaileApp(options) {
|
|
|
172
279
|
return session;
|
|
173
280
|
},
|
|
174
281
|
connect,
|
|
282
|
+
onSessionChange(handler) {
|
|
283
|
+
sessionListeners.add(handler);
|
|
284
|
+
return () => sessionListeners.delete(handler);
|
|
285
|
+
},
|
|
175
286
|
close
|
|
176
287
|
};
|
|
177
288
|
}
|
|
289
|
+
|
|
290
|
+
// app-sdk/src/connection-params.ts
|
|
291
|
+
var PARENT_ORIGIN_QUERY_PARAM = "skaileParentOrigin";
|
|
292
|
+
var DEFAULT_PARAMS_TIMEOUT_MS = 1e4;
|
|
293
|
+
function readParentOriginFromQuery(search) {
|
|
294
|
+
const value = new URLSearchParams(search).get(PARENT_ORIGIN_QUERY_PARAM);
|
|
295
|
+
return value && value.length > 0 ? value : null;
|
|
296
|
+
}
|
|
297
|
+
function isParamsMessage(data, appId) {
|
|
298
|
+
if (!data || typeof data !== "object") return false;
|
|
299
|
+
const m = data;
|
|
300
|
+
return m.type === "skaile_app_params" && m.appId === appId && typeof m.url === "string" && m.url.trim().length > 0 && typeof m.token === "string" && m.token.trim().length > 0;
|
|
301
|
+
}
|
|
302
|
+
function resolveAppConnectionParams(options) {
|
|
303
|
+
const { appId, timeoutMs = DEFAULT_PARAMS_TIMEOUT_MS } = options;
|
|
304
|
+
const win = options.windowRef ?? globalThis.window ?? null;
|
|
305
|
+
if (!win) {
|
|
306
|
+
return Promise.reject(
|
|
307
|
+
new Error("resolveAppConnectionParams requires a window (pass `windowRef` off-browser)")
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
const parent = win.parent;
|
|
311
|
+
if (!parent || parent === win) {
|
|
312
|
+
return Promise.reject(
|
|
313
|
+
new Error("app is not embedded \u2014 no parent frame to request params from")
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
const parentOrigin = options.parentOrigin ?? readParentOriginFromQuery(win.location.search);
|
|
317
|
+
if (!parentOrigin) {
|
|
318
|
+
return Promise.reject(
|
|
319
|
+
new Error(
|
|
320
|
+
`cannot determine the host origin: pass \`parentOrigin\` or set the \`${PARENT_ORIGIN_QUERY_PARAM}\` query param`
|
|
321
|
+
)
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
if (parentOrigin === "*" || parentOrigin === "null") {
|
|
325
|
+
return Promise.reject(new Error(`refusing to use an unpinnable host origin: ${parentOrigin}`));
|
|
326
|
+
}
|
|
327
|
+
return new Promise((resolve, reject) => {
|
|
328
|
+
let settled = false;
|
|
329
|
+
const cleanup = () => {
|
|
330
|
+
clearTimeout(timer);
|
|
331
|
+
win.removeEventListener("message", onMessage);
|
|
332
|
+
};
|
|
333
|
+
const timer = setTimeout(() => {
|
|
334
|
+
if (settled) return;
|
|
335
|
+
settled = true;
|
|
336
|
+
cleanup();
|
|
337
|
+
reject(
|
|
338
|
+
new Error(`timed out after ${timeoutMs}ms waiting for connection params from the host`)
|
|
339
|
+
);
|
|
340
|
+
}, timeoutMs);
|
|
341
|
+
function onMessage(event) {
|
|
342
|
+
if (settled) return;
|
|
343
|
+
if (event.origin !== parentOrigin || event.source !== parent) return;
|
|
344
|
+
if (!isParamsMessage(event.data, appId)) return;
|
|
345
|
+
settled = true;
|
|
346
|
+
cleanup();
|
|
347
|
+
resolve({ url: event.data.url, appId, token: event.data.token });
|
|
348
|
+
}
|
|
349
|
+
win.addEventListener("message", onMessage);
|
|
350
|
+
const ready = { type: "skaile_app_ready", appId };
|
|
351
|
+
try {
|
|
352
|
+
parent.postMessage(ready, parentOrigin);
|
|
353
|
+
} catch (err) {
|
|
354
|
+
settled = true;
|
|
355
|
+
cleanup();
|
|
356
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
}
|
|
178
360
|
function defineAppCapability(args) {
|
|
179
361
|
const inputSchema = z.toJSONSchema(args.input, {
|
|
180
362
|
target: "draft-07",
|
|
@@ -207,9 +389,7 @@ function defineAppCapability(args) {
|
|
|
207
389
|
// app-sdk/src/ws-transport.ts
|
|
208
390
|
var BEARER_SUBPROTOCOL_PREFIX = "skaile-bearer.";
|
|
209
391
|
function encodeBearerSubprotocol(token) {
|
|
210
|
-
const
|
|
211
|
-
let binary = "";
|
|
212
|
-
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
392
|
+
const binary = String.fromCharCode(...new TextEncoder().encode(token));
|
|
213
393
|
const b64 = btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
214
394
|
return BEARER_SUBPROTOCOL_PREFIX + b64;
|
|
215
395
|
}
|
|
@@ -227,9 +407,15 @@ function createWebSocketAppTransport(options) {
|
|
|
227
407
|
const messageHandlers = /* @__PURE__ */ new Set();
|
|
228
408
|
const disconnectHandlers = /* @__PURE__ */ new Set();
|
|
229
409
|
function connect() {
|
|
410
|
+
if (typeof sessionToken === "function") {
|
|
411
|
+
return Promise.resolve(sessionToken()).then(openSocket);
|
|
412
|
+
}
|
|
413
|
+
return openSocket(sessionToken);
|
|
414
|
+
}
|
|
415
|
+
function openSocket(token) {
|
|
230
416
|
return new Promise((resolve, reject) => {
|
|
231
417
|
let opened = false;
|
|
232
|
-
const socket = new WebSocketImpl(url, [encodeBearerSubprotocol(
|
|
418
|
+
const socket = new WebSocketImpl(url, [encodeBearerSubprotocol(token)]);
|
|
233
419
|
ws = socket;
|
|
234
420
|
const timer = setTimeout(() => {
|
|
235
421
|
if (opened) return;
|
|
@@ -290,6 +476,6 @@ function createWebSocketAppTransport(options) {
|
|
|
290
476
|
};
|
|
291
477
|
}
|
|
292
478
|
|
|
293
|
-
export { DEFAULT_HELLO_TIMEOUT_MS, consoleLogger, createSkaileApp, createWebSocketAppTransport, defineAppCapability, noopLogger };
|
|
479
|
+
export { AppHandshakeRefusedError, DEFAULT_HELLO_TIMEOUT_MS, DEFAULT_PARAMS_TIMEOUT_MS, PARENT_ORIGIN_QUERY_PARAM, consoleLogger, createSkaileApp, createWebSocketAppTransport, defineAppCapability, noopLogger, resolveAppConnectionParams };
|
|
294
480
|
//# sourceMappingURL=index.js.map
|
|
295
481
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../app-sdk/src/logger.ts","../../app-sdk/src/app.ts","../../app-sdk/src/define-app-capability.ts","../../app-sdk/src/ws-transport.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Minimal logger for the app SDK.\n *\n * The SDK is meant to run inside an embedded app (browser or a lightweight\n * Node service), so it deliberately does not pull in the framework's\n * `LogStore` / SQLite logging stack. Authors can pass their own {@link AppLogger}\n * (e.g. a bridge to their app's telemetry) or accept the console-backed default.\n *\n * @category App SDK\n * @since 3.4.0\n */\n\n/** Structured leveled logger the SDK and capability handlers emit through. */\nexport interface AppLogger {\n debug(message: string, data?: unknown): void;\n info(message: string, data?: unknown): void;\n warn(message: string, data?: unknown): void;\n error(message: string, data?: unknown): void;\n}\n\n/** A logger that discards everything. The default when none is supplied. */\nexport const noopLogger: AppLogger = {\n debug() {},\n info() {},\n warn() {},\n error() {},\n};\n\n/**\n * Console-backed logger, prefixing every line with `[skaile-app]`. Opt in by\n * passing `logger: consoleLogger` to {@link createSkaileApp} during development.\n */\nexport const consoleLogger: AppLogger = {\n debug(message, data) {\n console.debug(`[skaile-app] ${message}`, ...(data !== undefined ? [data] : []));\n },\n info(message, data) {\n console.info(`[skaile-app] ${message}`, ...(data !== undefined ? [data] : []));\n },\n warn(message, data) {\n console.warn(`[skaile-app] ${message}`, ...(data !== undefined ? [data] : []));\n },\n error(message, data) {\n console.error(`[skaile-app] ${message}`, ...(data !== undefined ? [data] : []));\n },\n};\n","/**\n * `createSkaileApp` — the thin app SDK that makes an embedded app\n * agent-controllable.\n *\n * An app becomes a *capability peer*, mirroring how the runner behaves toward\n * the platform:\n *\n * 1. Opens an authenticated back-connection (via an {@link AppTransport}).\n * 2. Performs the app handshake — sends `app_hello`, awaits `app_hello_ack`\n * (binding the connection to `session + appId`) or `app_hello_nack` (refusal).\n * 3. Registers its `app.*` capabilities via `capability_register`, stamping\n * each with `origin:{kind:'app', appId}` and `side:'app'`. Register /\n * deregister mid-session is supported.\n * 4. Answers inbound `capability_invoked` events by running the local handler\n * and replying with `capability_result` (a thrown handler rides back as\n * `{ error }`).\n *\n * The approval round-trip for `requiresApproval` capabilities is handled\n * entirely platform-side: the gateway parks the invocation for user approval\n * and only forwards `capability_invoked` to the app *after* approval, so the\n * app just marks the capability and answers when asked.\n *\n * @category App SDK\n * @since 3.4.0\n */\n\nimport type { Capability } from \"@skaile/workspaces/types\";\nimport { PROTOCOL_VERSION_STRING } from \"@skaile/workspaces/types\";\nimport type { DefinedAppCapability } from \"./define-app-capability.js\";\nimport { type AppLogger, noopLogger } from \"./logger.js\";\nimport type { AppServerMessage, AppTransport } from \"./transport.js\";\n\n/** Default timeout waiting for the gateway's `app_hello_ack`. */\nexport const DEFAULT_HELLO_TIMEOUT_MS = 10_000;\n\n/**\n * Options for {@link createSkaileApp}.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport interface SkaileAppOptions {\n /** The embedded app's stable id. Every registered capability is bound to it. */\n appId: string;\n /**\n * The app back-connection. Inject {@link createWebSocketAppTransport} in\n * production or a fake in tests. The transport carries the injected session\n * identity as its connection credential.\n */\n transport: AppTransport;\n /** Protocol version the SDK announces in `app_hello`. Defaults to the SDK's. */\n protocolVersion?: string;\n /** Timeout waiting for `app_hello_ack`. Defaults to {@link DEFAULT_HELLO_TIMEOUT_MS}. */\n helloTimeoutMs?: number;\n /** Logger for the SDK and (by default) capability handlers. Defaults to {@link noopLogger}. */\n logger?: AppLogger;\n}\n\n/** Result of a successful handshake — the bound session context. */\nexport interface AppSession {\n sessionId: string;\n userId?: string;\n}\n\n/**\n * A connected (or connectable) Skaile app. Register capabilities before or\n * after {@link connect}; post-connect changes are pushed to the gateway\n * immediately.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport interface SkaileApp {\n /**\n * Add (or replace, by name) an app capability. Registering the same name\n * again replaces it — mirrors the registry's replace-by-name semantics. When\n * already connected, a `capability_register` is sent immediately.\n */\n registerCapability(cap: DefinedAppCapability): void;\n /**\n * Remove a capability by name. No-op when unknown. When connected, a\n * `capability_deregister` is sent immediately so the tool disappears from the\n * agent's catalog.\n */\n deregisterCapability(name: string): void;\n /** Wire-format snapshot of the currently registered capabilities (origin stamped). */\n readonly capabilities: readonly Capability[];\n /** The bound session once connected; `null` before the handshake completes. */\n readonly session: AppSession | null;\n /**\n * Open the connection, perform the handshake, and register the current\n * capabilities. Resolves with the bound {@link AppSession}. Rejects if the\n * gateway refuses (`app_hello_nack`), the handshake times out, or the\n * transport disconnects mid-handshake.\n */\n connect(): Promise<AppSession>;\n /** Disconnect and tear down. Idempotent. */\n close(): Promise<void>;\n}\n\n/**\n * Create a {@link SkaileApp}. See the module doc for the peer lifecycle.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport function createSkaileApp(options: SkaileAppOptions): SkaileApp {\n const {\n appId,\n transport,\n protocolVersion = PROTOCOL_VERSION_STRING,\n helloTimeoutMs = DEFAULT_HELLO_TIMEOUT_MS,\n logger = noopLogger,\n } = options;\n\n const registry = new Map<string, DefinedAppCapability>();\n let session: AppSession | null = null;\n let unsubscribeMessage: (() => void) | null = null;\n let unsubscribeDisconnect: (() => void) | null = null;\n // Non-null only while a handshake is in flight; lets close() abort it\n // (clear the timer, reject the pending connect) instead of leaking a timer.\n let abortHandshake: ((err: Error) => void) | null = null;\n\n function toWire(cap: DefinedAppCapability): Capability {\n return {\n name: cap.name,\n displayName: cap.displayName,\n description: cap.description,\n inputSchema: cap.inputSchema,\n outputSchema: cap.outputSchema,\n side: \"app\",\n origin: { kind: \"app\", appId },\n scope: cap.scope,\n requiresApproval: cap.requiresApproval,\n kind: cap.kind,\n audience: cap.audience,\n promptFragment: cap.promptFragment,\n callTimeoutMs: cap.callTimeoutMs,\n render: cap.render,\n };\n }\n\n function registerCapability(cap: DefinedAppCapability): void {\n registry.set(cap.name, cap);\n if (session) {\n transport.send({ type: \"capability_register\", capabilities: [toWire(cap)] });\n logger.debug(\"registered capability (live)\", { name: cap.name });\n }\n }\n\n function deregisterCapability(name: string): void {\n if (!registry.delete(name)) return;\n if (session) {\n transport.send({ type: \"capability_deregister\", names: [name] });\n logger.debug(\"deregistered capability (live)\", { name });\n }\n }\n\n /** Dispatch an inbound invocation to the local handler; reply with the result. */\n async function handleInvocation(callId: string, name: string, input: unknown): Promise<void> {\n const cap = registry.get(name);\n if (!cap) {\n transport.send({\n type: \"capability_result\",\n callId,\n result: { error: `Unknown app capability: ${name}` },\n });\n return;\n }\n try {\n const validated = cap.inputZod.parse(input);\n const ctx = { sessionId: session?.sessionId ?? \"\", appId, log: logger };\n const result = await cap.handler(validated, ctx);\n transport.send({ type: \"capability_result\", callId, result });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.warn(\"capability handler failed\", { name, error: message });\n transport.send({ type: \"capability_result\", callId, result: { error: message } });\n }\n }\n\n function connect(): Promise<AppSession> {\n if (session) return Promise.resolve(session);\n if (abortHandshake) return Promise.reject(new Error(\"app connect() is already in progress\"));\n return new Promise<AppSession>((resolve, reject) => {\n let settled = false;\n let helloTimer: ReturnType<typeof setTimeout> | undefined;\n\n const finishHandshake = () => {\n if (helloTimer) clearTimeout(helloTimer);\n abortHandshake = null;\n };\n const fail = (err: Error) => {\n if (settled) return;\n settled = true;\n finishHandshake();\n reject(err);\n };\n // Expose the abort path to close() for the whole handshake window.\n abortHandshake = fail;\n\n const onMessage = (message: AppServerMessage) => {\n switch (message.type) {\n case \"app_hello_ack\": {\n if (settled) return;\n settled = true;\n finishHandshake();\n session = { sessionId: message.sessionId, userId: message.userId };\n logger.info(\"app connection established\", { appId, sessionId: session.sessionId });\n // Register the full current surface now that we are bound.\n const wire = Array.from(registry.values()).map(toWire);\n if (wire.length > 0) {\n transport.send({ type: \"capability_register\", capabilities: wire });\n }\n resolve(session);\n return;\n }\n case \"app_hello_nack\":\n fail(new Error(`app handshake refused: ${message.error}`));\n return;\n case \"capability_invoked\":\n void handleInvocation(message.callId, message.name, message.input);\n return;\n }\n };\n\n const onDisconnect = () => {\n fail(new Error(\"transport disconnected during handshake\"));\n };\n\n transport.connect().then(\n () => {\n // close() (or an early failure) may have aborted before connect resolved.\n if (settled) return;\n unsubscribeMessage = transport.onMessage(onMessage);\n unsubscribeDisconnect = transport.onDisconnect(onDisconnect);\n helloTimer = setTimeout(() => {\n fail(new Error(`app handshake timed out after ${helloTimeoutMs}ms`));\n }, helloTimeoutMs);\n transport.send({ type: \"app_hello\", appId, protocolVersion });\n },\n (err) => fail(err instanceof Error ? err : new Error(String(err))),\n );\n });\n }\n\n async function close(): Promise<void> {\n // Abort an in-flight handshake first — rejects the pending connect and\n // clears its timer so nothing leaks past close().\n abortHandshake?.(new Error(\"app closed during handshake\"));\n unsubscribeMessage?.();\n unsubscribeDisconnect?.();\n unsubscribeMessage = null;\n unsubscribeDisconnect = null;\n session = null;\n await transport.disconnect();\n }\n\n return {\n registerCapability,\n deregisterCapability,\n get capabilities() {\n return Array.from(registry.values()).map(toWire);\n },\n get session() {\n return session;\n },\n connect,\n close,\n };\n}\n","/**\n * `defineAppCapability` — author-friendly factory for an embedded app's\n * agent-controllable capabilities.\n *\n * It mirrors the runner's `defineCapability` (`@skaile/workspaces/runner`) but\n * is scoped to the app side: the handler always runs *in the app*, so `side` is\n * fixed to `'app'` and the author never passes it. The originating `appId` is\n * known to the SDK, not the individual capability, so `origin` is stamped by\n * {@link createSkaileApp} at registration time — not here.\n *\n * @category App SDK\n * @since 3.4.0\n */\n\nimport type { CapabilityAudience, CapabilityOrigin, RenderSpec } from \"@skaile/workspaces/types\";\nimport * as z from \"zod\";\nimport type { AppLogger } from \"./logger.js\";\n\n/**\n * Per-invocation context passed to an app capability handler. Analogous to the\n * runner's `HandlerContext`, but carries the `appId` and the SDK's\n * {@link AppLogger} (there is no server-side `capability:<origin>:<name>` log\n * slice inside the app process).\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport type AppHandlerContext = {\n /** The session this app connection is bound to (from the hello ack). */\n sessionId: string;\n /** The embedded app's id. */\n appId: string;\n /** Logger to emit through; defaults to the SDK's configured logger. */\n log: AppLogger;\n};\n\n/**\n * Author-facing arguments to {@link defineAppCapability}. Mirrors the wire\n * {@link import(\"@skaile/workspaces/types\").Capability} fields an app may set,\n * minus `side` / `origin` (fixed / stamped by the SDK) and the runtime-only\n * fields that only make sense platform-side.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport type DefineAppCapabilityArgs<\n T extends z.ZodTypeAny,\n R extends z.ZodTypeAny | undefined = undefined,\n> = {\n /**\n * Stable, session-unique name. Convention: `app.<sector>.<verb>` — e.g.\n * `app.ui.switch_contract` (UI sector) or `app.backend.insert_contract`\n * (backend sector). v1 distinguishes sectors by this naming convention.\n */\n name: string;\n /** Human-readable label for user-facing surfaces; falls back to `name`. */\n displayName?: string;\n /** Human/LLM-readable summary; shown to the agent at tool registration. */\n description: string;\n /** Zod schema validating the invocation arguments. Converted to JSON Schema. */\n input: T;\n /** Optional Zod schema for the return value. */\n output?: R;\n /** Lifetime: `session` survives the whole session, `turn` is dropped at end-of-turn. */\n scope?: \"session\" | \"turn\";\n /**\n * When true, the platform shows a confirm UI and waits for user approval\n * before the invocation is forwarded to the app. Mark every mutation\n * (backend writes, destructive UI actions) with this.\n */\n requiresApproval?: boolean;\n /** Documentation hint: side-effect, render, or pure query. */\n kind?: \"effect\" | \"render\" | \"query\";\n /**\n * Audience scoping. Omit for the default (`['llm','user']`). App capabilities\n * default to LLM-only in the command palette (`isUserInvokable`); set\n * `['llm','user']` on the user-facing actions you want to appear in cmdK.\n */\n audience?: CapabilityAudience[];\n /** Appended to the system prompt's `<CAPABILITIES>` section. */\n promptFragment?: string;\n /** Per-capability override for the platform's result round-trip timeout. */\n callTimeoutMs?: number;\n /** Render contract for capabilities that produce a UI surface. */\n render?: RenderSpec;\n /** The handler — runs in the app. Receives validated input + {@link AppHandlerContext}. */\n handler: (\n args: z.infer<T>,\n ctx: AppHandlerContext,\n ) => Promise<R extends z.ZodTypeAny ? z.infer<R> : unknown>;\n};\n\n/**\n * A defined app capability: the wire-format fields the SDK registers (minus the\n * SDK-stamped `origin`), plus the Zod schemas and type-erased handler used for\n * runtime input validation and inbound dispatch.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport type DefinedAppCapability = {\n name: string;\n displayName?: string;\n description: string;\n inputSchema: Record<string, unknown>;\n outputSchema?: Record<string, unknown>;\n /** Always `'app'` — the handler runs in the embedded app. */\n side: \"app\";\n scope?: \"session\" | \"turn\";\n requiresApproval?: boolean;\n kind?: \"effect\" | \"render\" | \"query\";\n audience?: CapabilityAudience[];\n promptFragment?: string;\n callTimeoutMs?: number;\n render?: RenderSpec;\n /** Original Zod schema for runtime input validation. Not serialized to the wire. */\n readonly inputZod: z.ZodTypeAny;\n /** Original Zod schema for the optional output, if declared. Not serialized. */\n readonly outputZod?: z.ZodTypeAny;\n /** Type-erased handler dispatched on an inbound `capability_invoked`. */\n handler: (args: unknown, ctx: AppHandlerContext) => Promise<unknown>;\n};\n\n/** Origin kind an app capability always carries once stamped by the SDK. */\nexport type AppCapabilityOrigin = Extract<CapabilityOrigin, { kind: \"app\" }>;\n\n/**\n * Build a {@link DefinedAppCapability} from author-friendly Zod-typed args.\n * `side` is fixed to `'app'`; `origin` is added by the SDK at registration\n * time (it needs the SDK's `appId`).\n *\n * @example\n * ```ts\n * const switchContract = defineAppCapability({\n * name: 'app.ui.switch_contract',\n * description: 'Switch the visible contract in the app UI.',\n * input: z.object({ contractId: z.string() }),\n * handler: async ({ contractId }, ctx) => {\n * ctx.log.info('switching contract', { contractId });\n * ui.select(contractId);\n * },\n * });\n * ```\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport function defineAppCapability<\n T extends z.ZodTypeAny,\n R extends z.ZodTypeAny | undefined = undefined,\n>(args: DefineAppCapabilityArgs<T, R>): DefinedAppCapability {\n const inputSchema = z.toJSONSchema(args.input, {\n target: \"draft-07\",\n io: \"input\",\n }) as Record<string, unknown>;\n const outputSchema = args.output\n ? (z.toJSONSchema(args.output, {\n target: \"draft-07\",\n io: \"output\",\n }) as Record<string, unknown>)\n : undefined;\n\n return {\n name: args.name,\n displayName: args.displayName,\n description: args.description,\n inputSchema,\n outputSchema,\n side: \"app\",\n scope: args.scope,\n requiresApproval: args.requiresApproval,\n kind: args.kind,\n audience: args.audience,\n promptFragment: args.promptFragment,\n callTimeoutMs: args.callTimeoutMs,\n render: args.render,\n inputZod: args.input,\n outputZod: args.output,\n handler: args.handler as (args: unknown, ctx: AppHandlerContext) => Promise<unknown>,\n };\n}\n","/**\n * WebSocket-backed {@link AppTransport}.\n *\n * Browser-safe: uses the standard `WebSocket` global (no `ws` dependency, no\n * `node:*` imports), so it runs unchanged in an embedded app's iframe and in a\n * lightweight Node 22+ service. The injected session identity is presented as a\n * bearer **subprotocol** — the same `skaile-bearer.<base64url>` scheme the\n * platform gateway already decodes for runner connections — so the gateway\n * verifies identity at the WS upgrade, before any `app_hello` is processed.\n *\n * Frames are bare JSON of the app-channel messages (no `MessageV2` envelope),\n * matching the runner peer's on-the-wire convention.\n *\n * @category App SDK\n * @since 3.4.0\n */\n\nimport type { AppClientMessage, AppServerMessage, AppTransport } from \"./transport.js\";\n\n/** Matches `BEARER_SUBPROTOCOL_PREFIX` in `@skaile/workspaces/transport`'s ws/auth. */\nconst BEARER_SUBPROTOCOL_PREFIX = \"skaile-bearer.\";\n\n/** Encode a bearer token as a WS subprotocol token (base64url, no padding). */\nfunction encodeBearerSubprotocol(token: string): string {\n // Encode via UTF-8 bytes so a non-ASCII token doesn't make `btoa` throw\n // (btoa is Latin-1 only). The gateway decodes the mirror of this.\n const bytes = new TextEncoder().encode(token);\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n const b64 = btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n return BEARER_SUBPROTOCOL_PREFIX + b64;\n}\n\n/** Minimal structural view of the parts of the `WebSocket` API the transport uses. */\ntype WebSocketLike = {\n readonly readyState: number;\n send(data: string): void;\n close(code?: number, reason?: string): void;\n addEventListener(type: \"open\", handler: () => void): void;\n addEventListener(type: \"close\", handler: () => void): void;\n addEventListener(type: \"error\", handler: () => void): void;\n addEventListener(type: \"message\", handler: (ev: { data: unknown }) => void): void;\n};\n\n/** Constructor shape for a `WebSocket` implementation (global or injected). */\nexport type WebSocketFactory = new (url: string, protocols?: string | string[]) => WebSocketLike;\n\n/**\n * Options for {@link createWebSocketAppTransport}.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport interface WebSocketAppTransportOptions {\n /** The gateway app-channel URL (e.g. `wss://platform/app-connect`). */\n url: string;\n /**\n * The injected session identity token. Presented as the WS bearer\n * subprotocol; the gateway verifies it and resolves the session/user.\n */\n sessionToken: string;\n /**\n * WebSocket implementation. Defaults to the `WebSocket` global (browser /\n * Node 22+). Injectable for tests or non-standard runtimes.\n */\n WebSocketImpl?: WebSocketFactory;\n /** Connect timeout in ms. Default 10000. */\n connectTimeoutMs?: number;\n}\n\nconst OPEN = 1;\n\n/**\n * Create a WebSocket-backed {@link AppTransport}. The session token is sent as a\n * bearer subprotocol at connect time; app-channel messages are JSON-framed.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport function createWebSocketAppTransport(options: WebSocketAppTransportOptions): AppTransport {\n const { url, sessionToken, connectTimeoutMs = 10_000 } = options;\n const resolved =\n options.WebSocketImpl ?? (globalThis as { WebSocket?: WebSocketFactory }).WebSocket;\n if (!resolved) {\n throw new Error(\n \"No WebSocket implementation available. Pass `WebSocketImpl` in a runtime without a global WebSocket.\",\n );\n }\n const WebSocketImpl: WebSocketFactory = resolved;\n\n let ws: WebSocketLike | null = null;\n const messageHandlers = new Set<(m: AppServerMessage) => void>();\n const disconnectHandlers = new Set<() => void>();\n\n function connect(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n let opened = false;\n const socket = new WebSocketImpl(url, [encodeBearerSubprotocol(sessionToken)]);\n ws = socket;\n\n const timer = setTimeout(() => {\n if (opened) return;\n socket.close(4008, \"connect timeout\");\n reject(new Error(`app transport connect timed out after ${connectTimeoutMs}ms`));\n }, connectTimeoutMs);\n\n socket.addEventListener(\"open\", () => {\n opened = true;\n clearTimeout(timer);\n resolve();\n });\n socket.addEventListener(\"error\", () => {\n if (!opened) {\n clearTimeout(timer);\n reject(new Error(\"app transport connection error\"));\n }\n });\n socket.addEventListener(\"close\", () => {\n clearTimeout(timer);\n if (ws === socket) ws = null;\n for (const h of disconnectHandlers) h();\n });\n socket.addEventListener(\"message\", (ev) => {\n let parsed: AppServerMessage;\n try {\n parsed = JSON.parse(String(ev.data)) as AppServerMessage;\n } catch {\n return;\n }\n for (const h of messageHandlers) h(parsed);\n });\n });\n }\n\n return {\n connect,\n async disconnect() {\n const socket = ws;\n ws = null;\n socket?.close(1000, \"client closed\");\n },\n send(message: AppClientMessage) {\n if (!ws || ws.readyState !== OPEN) {\n throw new Error(\"app transport is not connected\");\n }\n ws.send(JSON.stringify(message));\n },\n onMessage(handler) {\n messageHandlers.add(handler);\n return () => messageHandlers.delete(handler);\n },\n onDisconnect(handler) {\n disconnectHandlers.add(handler);\n return () => disconnectHandlers.delete(handler);\n },\n get connected() {\n return ws?.readyState === OPEN;\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../app-sdk/src/logger.ts","../../app-sdk/src/app.ts","../../app-sdk/src/connection-params.ts","../../app-sdk/src/define-app-capability.ts","../../app-sdk/src/ws-transport.ts"],"names":[],"mappings":";;;;;AAqBO,IAAM,UAAA,GAAwB;AAAA,EACnC,KAAA,GAAQ;AAAA,EAAC,CAAA;AAAA,EACT,IAAA,GAAO;AAAA,EAAC,CAAA;AAAA,EACR,IAAA,GAAO;AAAA,EAAC,CAAA;AAAA,EACR,KAAA,GAAQ;AAAA,EAAC;AACX;AAMO,IAAM,aAAA,GAA2B;AAAA,EACtC,KAAA,CAAM,SAAS,IAAA,EAAM;AACnB,IAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,aAAA,EAAgB,OAAO,CAAA,CAAA,EAAI,GAAI,IAAA,KAAS,MAAA,GAAY,CAAC,IAAI,CAAA,GAAI,EAAG,CAAA;AAAA,EAChF,CAAA;AAAA,EACA,IAAA,CAAK,SAAS,IAAA,EAAM;AAClB,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,aAAA,EAAgB,OAAO,CAAA,CAAA,EAAI,GAAI,IAAA,KAAS,MAAA,GAAY,CAAC,IAAI,CAAA,GAAI,EAAG,CAAA;AAAA,EAC/E,CAAA;AAAA,EACA,IAAA,CAAK,SAAS,IAAA,EAAM;AAClB,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,aAAA,EAAgB,OAAO,CAAA,CAAA,EAAI,GAAI,IAAA,KAAS,MAAA,GAAY,CAAC,IAAI,CAAA,GAAI,EAAG,CAAA;AAAA,EAC/E,CAAA;AAAA,EACA,KAAA,CAAM,SAAS,IAAA,EAAM;AACnB,IAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,aAAA,EAAgB,OAAO,CAAA,CAAA,EAAI,GAAI,IAAA,KAAS,MAAA,GAAY,CAAC,IAAI,CAAA,GAAI,EAAG,CAAA;AAAA,EAChF;AACF;;;ACRO,IAAM,wBAAA,GAA2B;AAGxC,IAAM,iBAAA,GAAgD;AAAA,EACpD,WAAA,EAAa,CAAA;AAAA,EACb,MAAA,EAAQ,GAAA;AAAA,EACR,KAAA,EAAO;AACT,CAAA;AA0BO,IAAM,wBAAA,GAAN,cAAuC,KAAA,CAAM;AAAA,EACzC,SAAA;AAAA,EACT,WAAA,CAAY,QAAgB,SAAA,EAAoB;AAC9C,IAAA,KAAA,CAAM,CAAA,uBAAA,EAA0B,MAAM,CAAA,CAAE,CAAA;AACxC,IAAA,IAAA,CAAK,IAAA,GAAO,0BAAA;AACZ,IAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AAAA,EACnB;AACF;AAuFA,IAAM,KAAA,GAAQ,CAAC,EAAA,KAAe,IAAI,OAAA,CAAc,CAAC,CAAA,KAAM,UAAA,CAAW,CAAA,EAAG,EAAE,CAAC,CAAA;AAQjE,SAAS,gBAAgB,OAAA,EAAsC;AACpE,EAAA,MAAM;AAAA,IACJ,KAAA;AAAA,IACA,SAAA;AAAA,IACA,eAAA,GAAkB,uBAAA;AAAA,IAClB,cAAA,GAAiB,wBAAA;AAAA,IACjB,MAAA,GAAS;AAAA,GACX,GAAI,OAAA;AAEJ,EAAA,MAAM,gBAAA,GAAmB,QAAQ,SAAA,KAAc,KAAA;AAC/C,EAAA,MAAM,MAAA,GAAqC;AAAA,IACzC,GAAG,iBAAA;AAAA,IACH,GAAI,OAAA,CAAQ,SAAA,KAAc,KAAA,GAAQ,KAAK,OAAA,CAAQ;AAAA,GACjD;AAEA,EAAA,MAAM,QAAA,uBAAe,GAAA,EAAkC;AACvD,EAAA,MAAM,gBAAA,uBAAuB,GAAA,EAAoC;AACjE,EAAA,IAAI,OAAA,GAA6B,IAAA;AACjC,EAAA,IAAI,MAAA,GAAS,KAAA;AACb,EAAA,IAAI,UAAA,GAAa,KAAA;AACjB,EAAA,IAAI,UAAA,GAAa,KAAA;AAIjB,EAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,EAAA,IAAI,kBAAA,GAA0C,IAAA;AAC9C,EAAA,IAAI,qBAAA,GAA6C,IAAA;AACjD,EAAA,IAAI,cAAA;AAEJ,EAAA,IAAI,aAAA,GAAqC,IAAA;AAGzC,EAAA,IAAI,OAAA,GAA0E,IAAA;AAE9E,EAAA,SAAS,OAAO,GAAA,EAAuC;AACrD,IAAA,OAAO;AAAA,MACL,MAAM,GAAA,CAAI,IAAA;AAAA,MACV,aAAa,GAAA,CAAI,WAAA;AAAA,MACjB,aAAa,GAAA,CAAI,WAAA;AAAA,MACjB,aAAa,GAAA,CAAI,WAAA;AAAA,MACjB,cAAc,GAAA,CAAI,YAAA;AAAA,MAClB,IAAA,EAAM,KAAA;AAAA,MACN,MAAA,EAAQ,EAAE,IAAA,EAAM,KAAA,EAAO,KAAA,EAAM;AAAA,MAC7B,OAAO,GAAA,CAAI,KAAA;AAAA,MACX,kBAAkB,GAAA,CAAI,gBAAA;AAAA,MACtB,MAAM,GAAA,CAAI,IAAA;AAAA,MACV,UAAU,GAAA,CAAI,QAAA;AAAA,MACd,gBAAgB,GAAA,CAAI,cAAA;AAAA,MACpB,eAAe,GAAA,CAAI,aAAA;AAAA,MACnB,QAAQ,GAAA,CAAI;AAAA,KACd;AAAA,EACF;AAEA,EAAA,SAAS,WAAW,IAAA,EAA+B;AACjD,IAAA,OAAA,GAAU,IAAA;AACV,IAAA,KAAA,MAAW,CAAA,IAAK,gBAAA,EAAkB,CAAA,CAAE,IAAI,CAAA;AAAA,EAC1C;AAEA,EAAA,SAAS,mBAAmB,GAAA,EAAiC;AAC3D,IAAA,QAAA,CAAS,GAAA,CAAI,GAAA,CAAI,IAAA,EAAM,GAAG,CAAA;AAC1B,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,SAAA,CAAU,IAAA,CAAK,EAAE,IAAA,EAAM,qBAAA,EAAuB,YAAA,EAAc,CAAC,MAAA,CAAO,GAAG,CAAC,CAAA,EAAG,CAAA;AAC3E,MAAA,MAAA,CAAO,MAAM,8BAAA,EAAgC,EAAE,IAAA,EAAM,GAAA,CAAI,MAAM,CAAA;AAAA,IACjE;AAAA,EACF;AAEA,EAAA,SAAS,qBAAqB,IAAA,EAAoB;AAChD,IAAA,IAAI,CAAC,QAAA,CAAS,MAAA,CAAO,IAAI,CAAA,EAAG;AAC5B,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,SAAA,CAAU,IAAA,CAAK,EAAE,IAAA,EAAM,uBAAA,EAAyB,OAAO,CAAC,IAAI,GAAG,CAAA;AAC/D,MAAA,MAAA,CAAO,KAAA,CAAM,gCAAA,EAAkC,EAAE,IAAA,EAAM,CAAA;AAAA,IACzD;AAAA,EACF;AAGA,EAAA,eAAe,gBAAA,CAAiB,MAAA,EAAgB,IAAA,EAAc,KAAA,EAA+B;AAC3F,IAAA,MAAM,GAAA,GAAM,QAAA,CAAS,GAAA,CAAI,IAAI,CAAA;AAC7B,IAAA,IAAI,CAAC,GAAA,EAAK;AACR,MAAA,SAAA,CAAU,IAAA,CAAK;AAAA,QACb,IAAA,EAAM,mBAAA;AAAA,QACN,MAAA;AAAA,QACA,MAAA,EAAQ,EAAE,KAAA,EAAO,CAAA,wBAAA,EAA2B,IAAI,CAAA,CAAA;AAAG,OACpD,CAAA;AACD,MAAA;AAAA,IACF;AACA,IAAA,IAAI;AACF,MAAA,MAAM,SAAA,GAAY,GAAA,CAAI,QAAA,CAAS,KAAA,CAAM,KAAK,CAAA;AAC1C,MAAA,MAAM,GAAA,GAAM,EAAE,SAAA,EAAW,OAAA,EAAS,aAAa,EAAA,EAAI,KAAA,EAAO,KAAK,MAAA,EAAO;AACtE,MAAA,MAAM,MAAA,GAAS,MAAM,GAAA,CAAI,OAAA,CAAQ,WAAW,GAAG,CAAA;AAC/C,MAAA,SAAA,CAAU,KAAK,EAAE,IAAA,EAAM,mBAAA,EAAqB,MAAA,EAAQ,QAAQ,CAAA;AAAA,IAC9D,SAAS,GAAA,EAAK;AACZ,MAAA,MAAM,UAAU,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG,CAAA;AAC/D,MAAA,MAAA,CAAO,KAAK,2BAAA,EAA6B,EAAE,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA;AACjE,MAAA,SAAA,CAAU,IAAA,CAAK,EAAE,IAAA,EAAM,mBAAA,EAAqB,MAAA,EAAQ,QAAQ,EAAE,KAAA,EAAO,OAAA,EAAQ,EAAG,CAAA;AAAA,IAClF;AAAA,EACF;AAEA,EAAA,SAAS,UAAU,OAAA,EAAiC;AAClD,IAAA,QAAQ,QAAQ,IAAA;AAAM,MACpB,KAAK,eAAA,EAAiB;AACpB,QAAA,MAAM,QAAoB,EAAE,SAAA,EAAW,QAAQ,SAAA,EAAW,MAAA,EAAQ,QAAQ,MAAA,EAAO;AACjF,QAAA,OAAA,EAAS,MAAA,CAAO,MAAM,KAAK,CAAA;AAC3B,QAAA;AAAA,MACF;AAAA,MACA,KAAK,gBAAA;AACH,QAAA,OAAA,EAAS,MAAA,CAAO,IAAI,wBAAA,CAAyB,OAAA,CAAQ,OAAO,OAAA,CAAQ,SAAA,KAAc,IAAI,CAAC,CAAA;AACvF,QAAA;AAAA,MACF,KAAK,oBAAA;AACH,QAAA,KAAK,iBAAiB,OAAA,CAAQ,MAAA,EAAQ,OAAA,CAAQ,IAAA,EAAM,QAAQ,KAAK,CAAA;AACjE,QAAA;AAAA;AACJ,EACF;AAEA,EAAA,SAAS,YAAA,GAAqB;AAC5B,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,OAAA,CAAQ,MAAA,CAAO,IAAI,KAAA,CAAM,yCAAyC,CAAC,CAAA;AACnE,MAAA;AAAA,IACF;AACA,IAAA,IAAI,MAAA,IAAU,CAAC,OAAA,EAAS;AACxB,IAAA,MAAA,CAAO,IAAA,CAAK,qBAAA,EAAuB,EAAE,KAAA,EAAO,CAAA;AAC5C,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,IAAI,CAAC,gBAAA,EAAkB;AAGvB,IAAA,UAAA,GAAa,IAAA;AACb,IAAA,MAAM,SAAA,GAAY,KAAA;AAClB,IAAA,KAAK,aAAA,EAAc,CAAE,OAAA,CAAQ,MAAM;AAEjC,MAAA,IAAI,KAAA,KAAU,WAAW,UAAA,GAAa,KAAA;AAAA,IACxC,CAAC,CAAA;AAAA,EACH;AAEA,EAAA,SAAS,gBAAA,GAAyB;AAChC,IAAA,IAAI,UAAA,EAAY;AAChB,IAAA,kBAAA,GAAqB,SAAA,CAAU,UAAU,SAAS,CAAA;AAClD,IAAA,qBAAA,GAAwB,SAAA,CAAU,aAAa,YAAY,CAAA;AAC3D,IAAA,UAAA,GAAa,IAAA;AAAA,EACf;AAGA,EAAA,SAAS,gBAAA,GAAwC;AAC/C,IAAA,gBAAA,EAAiB;AACjB,IAAA,OAAO,SAAA,CAAU,SAAQ,CAAE,IAAA;AAAA,MACzB,MACE,IAAI,OAAA,CAAoB,CAAC,SAAS,MAAA,KAAW;AAC3C,QAAA,IAAI,MAAA,EAAQ;AACV,UAAA,MAAA,CAAO,IAAI,KAAA,CAAM,6BAA6B,CAAC,CAAA;AAC/C,UAAA;AAAA,QACF;AACA,QAAA,IAAI,OAAA,GAAU,KAAA;AACd,QAAA,MAAM,KAAA,GAAQ,WAAW,MAAM;AAC7B,UAAA,OAAA,EAAS,OAAO,IAAI,KAAA,CAAM,CAAA,8BAAA,EAAiC,cAAc,IAAI,CAAC,CAAA;AAAA,QAChF,GAAG,cAAc,CAAA;AAEjB,QAAA,OAAA,GAAU;AAAA,UACR,MAAA,EAAQ,CAAC,GAAA,EAAK,KAAA,KAAU;AACtB,YAAA,IAAI,OAAA,EAAS;AACb,YAAA,OAAA,GAAU,IAAA;AACV,YAAA,YAAA,CAAa,KAAK,CAAA;AAClB,YAAA,OAAA,GAAU,IAAA;AACV,YAAA,IAAI,GAAA,IAAO,CAAC,KAAA,EAAO;AACjB,cAAA,MAAA,CAAO,GAAA,IAAO,IAAI,KAAA,CAAM,sBAAsB,CAAC,CAAA;AAC/C,cAAA;AAAA,YACF;AACA,YAAA,UAAA,CAAW,KAAK,CAAA;AAChB,YAAA,MAAA,CAAO,KAAK,4BAAA,EAA8B,EAAE,OAAO,SAAA,EAAW,KAAA,CAAM,WAAW,CAAA;AAG/E,YAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,QAAA,CAAS,QAAQ,CAAA,CAAE,IAAI,MAAM,CAAA;AACrD,YAAA,IAAI,IAAA,CAAK,SAAS,CAAA,EAAG;AACnB,cAAA,SAAA,CAAU,KAAK,EAAE,IAAA,EAAM,qBAAA,EAAuB,YAAA,EAAc,MAAM,CAAA;AAAA,YACpE;AACA,YAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,UACf;AAAA,SACF;AAEA,QAAA,SAAA,CAAU,KAAK,EAAE,IAAA,EAAM,WAAA,EAAa,KAAA,EAAO,iBAAiB,CAAA;AAAA,MAC9D,CAAC,CAAA;AAAA,MACH,CAAC,GAAA,KAAQ,OAAA,CAAQ,MAAA,CAAO,GAAA,YAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,KAAA,CAAM,MAAA,CAAO,GAAG,CAAC,CAAC;AAAA,KAC7E;AAAA,EACF;AAEA,EAAA,MAAM,SAAA,GAAY,CAAC,OAAA,KACjB,IAAA,CAAK,IAAI,MAAA,CAAO,KAAA,EAAO,MAAA,CAAO,MAAA,GAAS,KAAK,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,OAAA,GAAU,CAAC,CAAC,CAAA;AAGtE,EAAA,eAAe,aAAA,GAA+B;AAC5C,IAAA,MAAM,OAAA,GAAU,KAAA;AAChB,IAAA,KAAA,IAAS,OAAA,GAAU,CAAA,EAAG,OAAA,IAAW,MAAA,CAAO,aAAa,OAAA,EAAA,EAAW;AAC9D,MAAA,MAAM,IAAA,GAAO,UAAU,OAAO,CAAA;AAC9B,MAAA,MAAA,CAAO,MAAM,0BAAA,EAA4B,EAAE,OAAO,OAAA,EAAS,MAAA,EAAQ,MAAM,CAAA;AAIzE,MAAA,MAAM,IAAI,OAAA,CAAc,CAAC,CAAA,KAAM;AAC7B,QAAA,aAAA,GAAgB,CAAA;AAChB,QAAA,cAAA,GAAiB,UAAA,CAAW,GAAG,IAAI,CAAA;AAAA,MACrC,CAAC,CAAA;AACD,MAAA,aAAA,GAAgB,IAAA;AAChB,MAAA,IAAI,MAAA,IAAU,UAAU,OAAA,EAAS;AAC/B,QAAA,MAAA,CAAO,KAAA,CAAM,yBAAA,EAA2B,EAAE,KAAA,EAAO,SAAS,CAAA;AAC1D,QAAA;AAAA,MACF;AACA,MAAA,IAAI;AACF,QAAA,MAAM,gBAAA,EAAiB;AACvB,QAAA,MAAA,CAAO,IAAA,CAAK,+BAAA,EAAiC,EAAE,KAAA,EAAO,CAAA;AACtD,QAAA;AAAA,MACF,SAAS,GAAA,EAAK;AACZ,QAAA,IAAI,GAAA,YAAe,wBAAA,IAA4B,CAAC,GAAA,CAAI,SAAA,EAAW;AAC7D,UAAA,MAAA,CAAO,MAAM,mCAAA,EAAqC,EAAE,OAAO,KAAA,EAAO,GAAA,CAAI,SAAS,CAAA;AAC/E,UAAA;AAAA,QACF;AACA,QAAA,MAAA,CAAO,KAAK,8BAAA,EAAgC;AAAA,UAC1C,KAAA;AAAA,UACA,OAAA;AAAA,UACA,OAAO,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG;AAAA,SACvD,CAAA;AAAA,MACH;AAAA,IACF;AACA,IAAA,MAAA,CAAO,MAAM,uBAAA,EAAyB,EAAE,OAAO,QAAA,EAAU,MAAA,CAAO,aAAa,CAAA;AAAA,EAC/E;AAEA,EAAA,eAAe,OAAA,GAA+B;AAC5C,IAAA,IAAI,SAAS,OAAO,OAAA;AACpB,IAAA,IAAI,UAAA,EAAY,MAAM,IAAI,KAAA,CAAM,sCAAsC,CAAA;AACtE,IAAA,UAAA,GAAa,IAAA;AAGb,IAAA,MAAA,GAAS,KAAA;AACT,IAAA,IAAI;AACF,MAAA,KAAA,IAAS,OAAA,GAAU,KAAK,OAAA,EAAA,EAAW;AACjC,QAAA,IAAI;AACF,UAAA,OAAO,MAAM,gBAAA,EAAiB;AAAA,QAChC,SAAS,GAAA,EAAK;AAIZ,UAAA,MAAM,SAAA,GAAY,GAAA,YAAe,wBAAA,IAA4B,GAAA,CAAI,SAAA;AACjE,UAAA,IAAI,CAAC,SAAA,IAAa,CAAC,oBAAoB,OAAA,IAAW,MAAA,CAAO,eAAe,MAAA,EAAQ;AAC9E,YAAA,MAAM,GAAA;AAAA,UACR;AACA,UAAA,MAAA,CAAO,KAAA,CAAM,6CAAA,EAA+C,EAAE,KAAA,EAAO,SAAS,CAAA;AAC9E,UAAA,MAAM,KAAA,CAAM,SAAA,CAAU,OAAO,CAAC,CAAA;AAAA,QAChC;AAAA,MACF;AAAA,IACF,CAAA,SAAE;AACA,MAAA,UAAA,GAAa,KAAA;AAAA,IACf;AAAA,EACF;AAEA,EAAA,eAAe,KAAA,GAAuB;AACpC,IAAA,MAAA,GAAS,IAAA;AACT,IAAA,KAAA,EAAA;AACA,IAAA,YAAA,CAAa,cAAc,CAAA;AAG3B,IAAA,aAAA,IAAgB;AAChB,IAAA,aAAA,GAAgB,IAAA;AAChB,IAAA,UAAA,GAAa,KAAA;AAGb,IAAA,OAAA,EAAS,MAAA,CAAO,IAAI,KAAA,CAAM,6BAA6B,CAAC,CAAA;AACxD,IAAA,kBAAA,IAAqB;AACrB,IAAA,qBAAA,IAAwB;AACxB,IAAA,kBAAA,GAAqB,IAAA;AACrB,IAAA,qBAAA,GAAwB,IAAA;AACxB,IAAA,UAAA,GAAa,KAAA;AACb,IAAA,OAAA,GAAU,IAAA;AACV,IAAA,MAAM,UAAU,UAAA,EAAW;AAAA,EAC7B;AAEA,EAAA,OAAO;AAAA,IACL,kBAAA;AAAA,IACA,oBAAA;AAAA,IACA,IAAI,YAAA,GAAe;AACjB,MAAA,OAAO,MAAM,IAAA,CAAK,QAAA,CAAS,QAAQ,CAAA,CAAE,IAAI,MAAM,CAAA;AAAA,IACjD,CAAA;AAAA,IACA,IAAI,OAAA,GAAU;AACZ,MAAA,OAAO,OAAA;AAAA,IACT,CAAA;AAAA,IACA,OAAA;AAAA,IACA,gBAAgB,OAAA,EAAS;AACvB,MAAA,gBAAA,CAAiB,IAAI,OAAO,CAAA;AAC5B,MAAA,OAAO,MAAM,gBAAA,CAAiB,MAAA,CAAO,OAAO,CAAA;AAAA,IAC9C,CAAA;AAAA,IACA;AAAA,GACF;AACF;;;AC1aO,IAAM,yBAAA,GAA4B;AAGlC,IAAM,yBAAA,GAA4B;AAqEzC,SAAS,0BAA0B,MAAA,EAA+B;AAChE,EAAA,MAAM,QAAQ,IAAI,eAAA,CAAgB,MAAM,CAAA,CAAE,IAAI,yBAAyB,CAAA;AACvE,EAAA,OAAO,KAAA,IAAS,KAAA,CAAM,MAAA,GAAS,CAAA,GAAI,KAAA,GAAQ,IAAA;AAC7C;AAEA,SAAS,eAAA,CAAgB,MAAe,KAAA,EAA+C;AACrF,EAAA,IAAI,CAAC,IAAA,IAAQ,OAAO,IAAA,KAAS,UAAU,OAAO,KAAA;AAC9C,EAAA,MAAM,CAAA,GAAI,IAAA;AAGV,EAAA,OACE,CAAA,CAAE,IAAA,KAAS,mBAAA,IACX,CAAA,CAAE,KAAA,KAAU,SACZ,OAAO,CAAA,CAAE,GAAA,KAAQ,QAAA,IACjB,CAAA,CAAE,GAAA,CAAI,MAAK,CAAE,MAAA,GAAS,CAAA,IACtB,OAAO,CAAA,CAAE,KAAA,KAAU,YACnB,CAAA,CAAE,KAAA,CAAM,IAAA,EAAK,CAAE,MAAA,GAAS,CAAA;AAE5B;AAoBO,SAAS,2BACd,OAAA,EAC8B;AAC9B,EAAA,MAAM,EAAE,KAAA,EAAO,SAAA,GAAY,yBAAA,EAA0B,GAAI,OAAA;AACzD,EAAA,MAAM,GAAA,GACJ,OAAA,CAAQ,SAAA,IAAc,UAAA,CAAqD,MAAA,IAAU,IAAA;AACvF,EAAA,IAAI,CAAC,GAAA,EAAK;AACR,IAAA,OAAO,OAAA,CAAQ,MAAA;AAAA,MACb,IAAI,MAAM,6EAA6E;AAAA,KACzF;AAAA,EACF;AAEA,EAAA,MAAM,SAAS,GAAA,CAAI,MAAA;AAGnB,EAAA,IAAI,CAAC,MAAA,IAAW,MAAA,KAAwB,GAAA,EAAiB;AACvD,IAAA,OAAO,OAAA,CAAQ,MAAA;AAAA,MACb,IAAI,MAAM,mEAA8D;AAAA,KAC1E;AAAA,EACF;AAEA,EAAA,MAAM,eAAe,OAAA,CAAQ,YAAA,IAAgB,yBAAA,CAA0B,GAAA,CAAI,SAAS,MAAM,CAAA;AAC1F,EAAA,IAAI,CAAC,YAAA,EAAc;AACjB,IAAA,OAAO,OAAA,CAAQ,MAAA;AAAA,MACb,IAAI,KAAA;AAAA,QACF,wEAAwE,yBAAyB,CAAA,cAAA;AAAA;AACnG,KACF;AAAA,EACF;AAIA,EAAA,IAAI,YAAA,KAAiB,GAAA,IAAO,YAAA,KAAiB,MAAA,EAAQ;AACnD,IAAA,OAAO,QAAQ,MAAA,CAAO,IAAI,MAAM,CAAA,2CAAA,EAA8C,YAAY,EAAE,CAAC,CAAA;AAAA,EAC/F;AAEA,EAAA,OAAO,IAAI,OAAA,CAA6B,CAAC,OAAA,EAAS,MAAA,KAAW;AAC3D,IAAA,IAAI,OAAA,GAAU,KAAA;AAEd,IAAA,MAAM,UAAU,MAAM;AACpB,MAAA,YAAA,CAAa,KAAK,CAAA;AAClB,MAAA,GAAA,CAAI,mBAAA,CAAoB,WAAW,SAAS,CAAA;AAAA,IAC9C,CAAA;AAEA,IAAA,MAAM,KAAA,GAAQ,WAAW,MAAM;AAC7B,MAAA,IAAI,OAAA,EAAS;AACb,MAAA,OAAA,GAAU,IAAA;AACV,MAAA,OAAA,EAAQ;AACR,MAAA,MAAA;AAAA,QACE,IAAI,KAAA,CAAM,CAAA,gBAAA,EAAmB,SAAS,CAAA,8CAAA,CAAgD;AAAA,OACxF;AAAA,IACF,GAAG,SAAS,CAAA;AAEZ,IAAA,SAAS,UAAU,KAAA,EAA+B;AAChD,MAAA,IAAI,OAAA,EAAS;AAGb,MAAA,IAAI,KAAA,CAAM,MAAA,KAAW,YAAA,IAAgB,KAAA,CAAM,WAAW,MAAA,EAAQ;AAC9D,MAAA,IAAI,CAAC,eAAA,CAAgB,KAAA,CAAM,IAAA,EAAM,KAAK,CAAA,EAAG;AACzC,MAAA,OAAA,GAAU,IAAA;AACV,MAAA,OAAA,EAAQ;AACR,MAAA,OAAA,CAAQ,EAAE,GAAA,EAAK,KAAA,CAAM,IAAA,CAAK,GAAA,EAAK,OAAO,KAAA,EAAO,KAAA,CAAM,IAAA,CAAK,KAAA,EAAO,CAAA;AAAA,IACjE;AAEA,IAAA,GAAA,CAAI,gBAAA,CAAiB,WAAW,SAAS,CAAA;AAEzC,IAAA,MAAM,KAAA,GAA+B,EAAE,IAAA,EAAM,kBAAA,EAAoB,KAAA,EAAM;AACvE,IAAA,IAAI;AACF,MAAA,MAAA,CAAO,WAAA,CAAY,OAAO,YAAY,CAAA;AAAA,IACxC,SAAS,GAAA,EAAK;AACZ,MAAA,OAAA,GAAU,IAAA;AACV,MAAA,OAAA,EAAQ;AACR,MAAA,MAAA,CAAO,GAAA,YAAe,QAAQ,GAAA,GAAM,IAAI,MAAM,MAAA,CAAO,GAAG,CAAC,CAAC,CAAA;AAAA,IAC5D;AAAA,EACF,CAAC,CAAA;AACH;ACvEO,SAAS,oBAGd,IAAA,EAA2D;AAC3D,EAAA,MAAM,WAAA,GAAgB,CAAA,CAAA,YAAA,CAAa,IAAA,CAAK,KAAA,EAAO;AAAA,IAC7C,MAAA,EAAQ,UAAA;AAAA,IACR,EAAA,EAAI;AAAA,GACL,CAAA;AACD,EAAA,MAAM,YAAA,GAAe,IAAA,CAAK,MAAA,GACnB,CAAA,CAAA,YAAA,CAAa,KAAK,MAAA,EAAQ;AAAA,IAC3B,MAAA,EAAQ,UAAA;AAAA,IACR,EAAA,EAAI;AAAA,GACL,CAAA,GACD,MAAA;AAEJ,EAAA,OAAO;AAAA,IACL,MAAM,IAAA,CAAK,IAAA;AAAA,IACX,aAAa,IAAA,CAAK,WAAA;AAAA,IAClB,aAAa,IAAA,CAAK,WAAA;AAAA,IAClB,WAAA;AAAA,IACA,YAAA;AAAA,IACA,IAAA,EAAM,KAAA;AAAA,IACN,OAAO,IAAA,CAAK,KAAA;AAAA,IACZ,kBAAkB,IAAA,CAAK,gBAAA;AAAA,IACvB,MAAM,IAAA,CAAK,IAAA;AAAA,IACX,UAAU,IAAA,CAAK,QAAA;AAAA,IACf,gBAAgB,IAAA,CAAK,cAAA;AAAA,IACrB,eAAe,IAAA,CAAK,aAAA;AAAA,IACpB,QAAQ,IAAA,CAAK,MAAA;AAAA,IACb,UAAU,IAAA,CAAK,KAAA;AAAA,IACf,WAAW,IAAA,CAAK,MAAA;AAAA,IAChB,SAAS,IAAA,CAAK;AAAA,GAChB;AACF;;;AChKA,IAAM,yBAAA,GAA4B,gBAAA;AAGlC,SAAS,wBAAwB,KAAA,EAAuB;AAGtD,EAAA,MAAM,MAAA,GAAS,OAAO,YAAA,CAAa,GAAG,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,KAAK,CAAC,CAAA;AACrE,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,MAAM,CAAA,CAAE,QAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,OAAO,EAAE,CAAA;AAClF,EAAA,OAAO,yBAAA,GAA4B,GAAA;AACrC;AAwDA,IAAM,IAAA,GAAO,CAAA;AASN,SAAS,4BAA4B,OAAA,EAAqD;AAC/F,EAAA,MAAM,EAAE,GAAA,EAAK,YAAA,EAAc,gBAAA,GAAmB,KAAO,GAAI,OAAA;AACzD,EAAA,MAAM,QAAA,GACJ,OAAA,CAAQ,aAAA,IAAkB,UAAA,CAAgD,SAAA;AAC5E,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AACA,EAAA,MAAM,aAAA,GAAkC,QAAA;AAExC,EAAA,IAAI,EAAA,GAA2B,IAAA;AAC/B,EAAA,MAAM,eAAA,uBAAsB,GAAA,EAAmC;AAC/D,EAAA,MAAM,kBAAA,uBAAyB,GAAA,EAAgB;AAE/C,EAAA,SAAS,OAAA,GAAyB;AAIhC,IAAA,IAAI,OAAO,iBAAiB,UAAA,EAAY;AACtC,MAAA,OAAO,QAAQ,OAAA,CAAQ,YAAA,EAAc,CAAA,CAAE,KAAK,UAAU,CAAA;AAAA,IACxD;AACA,IAAA,OAAO,WAAW,YAAY,CAAA;AAAA,EAChC;AAEA,EAAA,SAAS,WAAW,KAAA,EAA8B;AAChD,IAAA,OAAO,IAAI,OAAA,CAAc,CAAC,OAAA,EAAS,MAAA,KAAW;AAC5C,MAAA,IAAI,MAAA,GAAS,KAAA;AACb,MAAA,MAAM,MAAA,GAAS,IAAI,aAAA,CAAc,GAAA,EAAK,CAAC,uBAAA,CAAwB,KAAK,CAAC,CAAC,CAAA;AACtE,MAAA,EAAA,GAAK,MAAA;AAEL,MAAA,MAAM,KAAA,GAAQ,WAAW,MAAM;AAC7B,QAAA,IAAI,MAAA,EAAQ;AACZ,QAAA,MAAA,CAAO,KAAA,CAAM,MAAM,iBAAiB,CAAA;AACpC,QAAA,MAAA,CAAO,IAAI,KAAA,CAAM,CAAA,sCAAA,EAAyC,gBAAgB,IAAI,CAAC,CAAA;AAAA,MACjF,GAAG,gBAAgB,CAAA;AAEnB,MAAA,MAAA,CAAO,gBAAA,CAAiB,QAAQ,MAAM;AACpC,QAAA,MAAA,GAAS,IAAA;AACT,QAAA,YAAA,CAAa,KAAK,CAAA;AAClB,QAAA,OAAA,EAAQ;AAAA,MACV,CAAC,CAAA;AACD,MAAA,MAAA,CAAO,gBAAA,CAAiB,SAAS,MAAM;AACrC,QAAA,IAAI,CAAC,MAAA,EAAQ;AACX,UAAA,YAAA,CAAa,KAAK,CAAA;AAClB,UAAA,MAAA,CAAO,IAAI,KAAA,CAAM,gCAAgC,CAAC,CAAA;AAAA,QACpD;AAAA,MACF,CAAC,CAAA;AACD,MAAA,MAAA,CAAO,gBAAA,CAAiB,SAAS,MAAM;AACrC,QAAA,YAAA,CAAa,KAAK,CAAA;AAClB,QAAA,IAAI,EAAA,KAAO,QAAQ,EAAA,GAAK,IAAA;AACxB,QAAA,KAAA,MAAW,CAAA,IAAK,oBAAoB,CAAA,EAAE;AAAA,MACxC,CAAC,CAAA;AACD,MAAA,MAAA,CAAO,gBAAA,CAAiB,SAAA,EAAW,CAAC,EAAA,KAAO;AACzC,QAAA,IAAI,MAAA;AACJ,QAAA,IAAI;AACF,UAAA,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,EAAA,CAAG,IAAI,CAAC,CAAA;AAAA,QACrC,CAAA,CAAA,MAAQ;AACN,UAAA;AAAA,QACF;AACA,QAAA,KAAA,MAAW,CAAA,IAAK,eAAA,EAAiB,CAAA,CAAE,MAAM,CAAA;AAAA,MAC3C,CAAC,CAAA;AAAA,IACH,CAAC,CAAA;AAAA,EACH;AAEA,EAAA,OAAO;AAAA,IACL,OAAA;AAAA,IACA,MAAM,UAAA,GAAa;AACjB,MAAA,MAAM,MAAA,GAAS,EAAA;AACf,MAAA,EAAA,GAAK,IAAA;AACL,MAAA,MAAA,EAAQ,KAAA,CAAM,KAAM,eAAe,CAAA;AAAA,IACrC,CAAA;AAAA,IACA,KAAK,OAAA,EAA2B;AAC9B,MAAA,IAAI,CAAC,EAAA,IAAM,EAAA,CAAG,UAAA,KAAe,IAAA,EAAM;AACjC,QAAA,MAAM,IAAI,MAAM,gCAAgC,CAAA;AAAA,MAClD;AACA,MAAA,EAAA,CAAG,IAAA,CAAK,IAAA,CAAK,SAAA,CAAU,OAAO,CAAC,CAAA;AAAA,IACjC,CAAA;AAAA,IACA,UAAU,OAAA,EAAS;AACjB,MAAA,eAAA,CAAgB,IAAI,OAAO,CAAA;AAC3B,MAAA,OAAO,MAAM,eAAA,CAAgB,MAAA,CAAO,OAAO,CAAA;AAAA,IAC7C,CAAA;AAAA,IACA,aAAa,OAAA,EAAS;AACpB,MAAA,kBAAA,CAAmB,IAAI,OAAO,CAAA;AAC9B,MAAA,OAAO,MAAM,kBAAA,CAAmB,MAAA,CAAO,OAAO,CAAA;AAAA,IAChD,CAAA;AAAA,IACA,IAAI,SAAA,GAAY;AACd,MAAA,OAAO,IAAI,UAAA,KAAe,IAAA;AAAA,IAC5B;AAAA,GACF;AACF","file":"index.js","sourcesContent":["/**\n * Minimal logger for the app SDK.\n *\n * The SDK is meant to run inside an embedded app (browser or a lightweight\n * Node service), so it deliberately does not pull in the framework's\n * `LogStore` / SQLite logging stack. Authors can pass their own {@link AppLogger}\n * (e.g. a bridge to their app's telemetry) or accept the console-backed default.\n *\n * @category App SDK\n * @since 3.4.0\n */\n\n/** Structured leveled logger the SDK and capability handlers emit through. */\nexport interface AppLogger {\n debug(message: string, data?: unknown): void;\n info(message: string, data?: unknown): void;\n warn(message: string, data?: unknown): void;\n error(message: string, data?: unknown): void;\n}\n\n/** A logger that discards everything. The default when none is supplied. */\nexport const noopLogger: AppLogger = {\n debug() {},\n info() {},\n warn() {},\n error() {},\n};\n\n/**\n * Console-backed logger, prefixing every line with `[skaile-app]`. Opt in by\n * passing `logger: consoleLogger` to {@link createSkaileApp} during development.\n */\nexport const consoleLogger: AppLogger = {\n debug(message, data) {\n console.debug(`[skaile-app] ${message}`, ...(data !== undefined ? [data] : []));\n },\n info(message, data) {\n console.info(`[skaile-app] ${message}`, ...(data !== undefined ? [data] : []));\n },\n warn(message, data) {\n console.warn(`[skaile-app] ${message}`, ...(data !== undefined ? [data] : []));\n },\n error(message, data) {\n console.error(`[skaile-app] ${message}`, ...(data !== undefined ? [data] : []));\n },\n};\n","/**\n * `createSkaileApp` — the thin app SDK that makes an embedded app\n * agent-controllable.\n *\n * An app becomes a *capability peer*, mirroring how the runner behaves toward\n * the platform:\n *\n * 1. Opens an authenticated back-connection (via an {@link AppTransport}).\n * 2. Performs the app handshake — sends `app_hello`, awaits `app_hello_ack`\n * (binding the connection to `session + appId`) or `app_hello_nack` (refusal).\n * 3. Registers its `app.*` capabilities via `capability_register`, stamping\n * each with `origin:{kind:'app', appId}` and `side:'app'`. Register /\n * deregister mid-session is supported.\n * 4. Answers inbound `capability_invoked` events by running the local handler\n * and replying with `capability_result` (a thrown handler rides back as\n * `{ error }`).\n *\n * The approval round-trip for `requiresApproval` capabilities is handled\n * entirely platform-side: the gateway parks the invocation for user approval\n * and only forwards `capability_invoked` to the app *after* approval, so the\n * app just marks the capability and answers when asked.\n *\n * The peer reconnects and replays its registrations on a dropped socket, and on\n * a *retryable* refusal — the app boots independently of the session's agent\n * container and can win that race. See {@link ReconnectOptions}.\n *\n * @category App SDK\n * @since 3.4.0\n */\n\nimport type { Capability } from \"@skaile/workspaces/types\";\nimport { PROTOCOL_VERSION_STRING } from \"@skaile/workspaces/types\";\nimport type { DefinedAppCapability } from \"./define-app-capability.js\";\nimport { type AppLogger, noopLogger } from \"./logger.js\";\nimport type { AppServerMessage, AppTransport } from \"./transport.js\";\n\n/** Default timeout waiting for the gateway's `app_hello_ack`. */\nexport const DEFAULT_HELLO_TIMEOUT_MS = 10_000;\n\n/** Defaults mirroring the agent `WebSocketClientTransport`'s backoff. */\nconst DEFAULT_RECONNECT: Required<ReconnectOptions> = {\n maxAttempts: 5,\n baseMs: 1_000,\n maxMs: 30_000,\n};\n\n/**\n * Reconnect / retry policy. Set `reconnect: false` on {@link SkaileAppOptions}\n * to disable entirely (one attempt, no replay).\n *\n * @category App SDK\n * @since 3.5.0\n */\nexport interface ReconnectOptions {\n /** Attempts before giving up. Default 5. */\n maxAttempts?: number;\n /** First backoff delay in ms; doubles each attempt. Default 1000. */\n baseMs?: number;\n /** Backoff ceiling in ms. Default 30000. */\n maxMs?: number;\n}\n\n/**\n * The gateway refused the handshake. `retryable` mirrors the wire field: `true`\n * for a transient refusal (the agent container is not up yet), `false` for a\n * permanent one (bad token, `appId` not permitted).\n *\n * @category App SDK\n * @since 3.5.0\n */\nexport class AppHandshakeRefusedError extends Error {\n readonly retryable: boolean;\n constructor(reason: string, retryable: boolean) {\n super(`app handshake refused: ${reason}`);\n this.name = \"AppHandshakeRefusedError\";\n this.retryable = retryable;\n }\n}\n\n/**\n * Options for {@link createSkaileApp}.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport interface SkaileAppOptions {\n /** The embedded app's stable id. Every registered capability is bound to it. */\n appId: string;\n /**\n * The app back-connection. Inject {@link createWebSocketAppTransport} in\n * production or a fake in tests. The transport carries the injected session\n * identity as its connection credential.\n *\n * With `reconnect` enabled the transport must be able to produce a **fresh**\n * token per attempt — the gateway consumes it at `app_hello`. Pass a\n * `SessionTokenProvider` rather than a bare string.\n */\n transport: AppTransport;\n /** Protocol version the SDK announces in `app_hello`. Defaults to the SDK's. */\n protocolVersion?: string;\n /** Timeout waiting for `app_hello_ack`. Defaults to {@link DEFAULT_HELLO_TIMEOUT_MS}. */\n helloTimeoutMs?: number;\n /** Logger for the SDK and (by default) capability handlers. Defaults to {@link noopLogger}. */\n logger?: AppLogger;\n /**\n * Reconnect policy, or `false` to disable. Defaults to\n * `{maxAttempts:5, baseMs:1000, maxMs:30000}`.\n *\n * @since 3.5.0\n */\n reconnect?: ReconnectOptions | false;\n}\n\n/** Result of a successful handshake — the bound session context. */\nexport interface AppSession {\n sessionId: string;\n userId?: string;\n}\n\n/**\n * A connected (or connectable) Skaile app. Register capabilities before or\n * after {@link connect}; post-connect changes are pushed to the gateway\n * immediately.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport interface SkaileApp {\n /**\n * Add (or replace, by name) an app capability. Registering the same name\n * again replaces it — mirrors the registry's replace-by-name semantics. When\n * already connected, a `capability_register` is sent immediately.\n */\n registerCapability(cap: DefinedAppCapability): void;\n /**\n * Remove a capability by name. No-op when unknown. When connected, a\n * `capability_deregister` is sent immediately so the tool disappears from the\n * agent's catalog.\n */\n deregisterCapability(name: string): void;\n /** Wire-format snapshot of the currently registered capabilities (origin stamped). */\n readonly capabilities: readonly Capability[];\n /** The bound session once connected; `null` before the handshake and while reconnecting. */\n readonly session: AppSession | null;\n /**\n * Open the connection, perform the handshake, and register the current\n * capabilities. Resolves with the bound {@link AppSession}. Rejects if the\n * gateway refuses permanently, the handshake times out, or the transport\n * disconnects mid-handshake. A *retryable* refusal is retried per the\n * reconnect policy before rejecting.\n */\n connect(): Promise<AppSession>;\n /**\n * Subscribe to session binding changes. Fires on **every** bind — including\n * the initial `connect()` — with the new {@link AppSession}, and with `null`\n * whenever the connection drops. Returns an unsubscribe function.\n *\n * @since 3.5.0\n */\n onSessionChange(handler: (session: AppSession | null) => void): () => void;\n /** Disconnect and tear down. Idempotent. Cancels any pending reconnect. */\n close(): Promise<void>;\n}\n\nconst delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));\n\n/**\n * Create a {@link SkaileApp}. See the module doc for the peer lifecycle.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport function createSkaileApp(options: SkaileAppOptions): SkaileApp {\n const {\n appId,\n transport,\n protocolVersion = PROTOCOL_VERSION_STRING,\n helloTimeoutMs = DEFAULT_HELLO_TIMEOUT_MS,\n logger = noopLogger,\n } = options;\n\n const reconnectEnabled = options.reconnect !== false;\n const policy: Required<ReconnectOptions> = {\n ...DEFAULT_RECONNECT,\n ...(options.reconnect === false ? {} : options.reconnect),\n };\n\n const registry = new Map<string, DefinedAppCapability>();\n const sessionListeners = new Set<(s: AppSession | null) => void>();\n let session: AppSession | null = null;\n let closed = false;\n let connecting = false;\n let subscribed = false;\n // Bumped by close(). A reconnect loop parked on its backoff timer captures the\n // epoch it started in and abandons if close() (or a close/connect cycle) has\n // since happened — otherwise it would resurrect a torn-down app.\n let epoch = 0;\n let unsubscribeMessage: (() => void) | null = null;\n let unsubscribeDisconnect: (() => void) | null = null;\n let reconnectTimer: ReturnType<typeof setTimeout> | undefined;\n /** Resolver of the backoff sleep, so close() can wake a parked loop. */\n let reconnectWake: (() => void) | null = null;\n\n /** Non-null only while a handshake is in flight. */\n let pending: { settle: (err: Error | null, s?: AppSession) => void } | null = null;\n\n function toWire(cap: DefinedAppCapability): Capability {\n return {\n name: cap.name,\n displayName: cap.displayName,\n description: cap.description,\n inputSchema: cap.inputSchema,\n outputSchema: cap.outputSchema,\n side: \"app\",\n origin: { kind: \"app\", appId },\n scope: cap.scope,\n requiresApproval: cap.requiresApproval,\n kind: cap.kind,\n audience: cap.audience,\n promptFragment: cap.promptFragment,\n callTimeoutMs: cap.callTimeoutMs,\n render: cap.render,\n };\n }\n\n function setSession(next: AppSession | null): void {\n session = next;\n for (const h of sessionListeners) h(next);\n }\n\n function registerCapability(cap: DefinedAppCapability): void {\n registry.set(cap.name, cap);\n if (session) {\n transport.send({ type: \"capability_register\", capabilities: [toWire(cap)] });\n logger.debug(\"registered capability (live)\", { name: cap.name });\n }\n }\n\n function deregisterCapability(name: string): void {\n if (!registry.delete(name)) return;\n if (session) {\n transport.send({ type: \"capability_deregister\", names: [name] });\n logger.debug(\"deregistered capability (live)\", { name });\n }\n }\n\n /** Dispatch an inbound invocation to the local handler; reply with the result. */\n async function handleInvocation(callId: string, name: string, input: unknown): Promise<void> {\n const cap = registry.get(name);\n if (!cap) {\n transport.send({\n type: \"capability_result\",\n callId,\n result: { error: `Unknown app capability: ${name}` },\n });\n return;\n }\n try {\n const validated = cap.inputZod.parse(input);\n const ctx = { sessionId: session?.sessionId ?? \"\", appId, log: logger };\n const result = await cap.handler(validated, ctx);\n transport.send({ type: \"capability_result\", callId, result });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.warn(\"capability handler failed\", { name, error: message });\n transport.send({ type: \"capability_result\", callId, result: { error: message } });\n }\n }\n\n function onMessage(message: AppServerMessage): void {\n switch (message.type) {\n case \"app_hello_ack\": {\n const bound: AppSession = { sessionId: message.sessionId, userId: message.userId };\n pending?.settle(null, bound);\n return;\n }\n case \"app_hello_nack\":\n pending?.settle(new AppHandshakeRefusedError(message.error, message.retryable === true));\n return;\n case \"capability_invoked\":\n void handleInvocation(message.callId, message.name, message.input);\n return;\n }\n }\n\n function onDisconnect(): void {\n if (pending) {\n pending.settle(new Error(\"transport disconnected during handshake\"));\n return;\n }\n if (closed || !session) return;\n logger.warn(\"app connection lost\", { appId });\n setSession(null);\n if (!reconnectEnabled) return;\n // The loop holds the same guard connect() uses: a manual connect() while it\n // runs would start a second handshake, and both would overwrite `pending`.\n connecting = true;\n const loopEpoch = epoch;\n void reconnectLoop().finally(() => {\n // A close()/connect() cycle since this loop started owns the guard now.\n if (epoch === loopEpoch) connecting = false;\n });\n }\n\n function ensureSubscribed(): void {\n if (subscribed) return;\n unsubscribeMessage = transport.onMessage(onMessage);\n unsubscribeDisconnect = transport.onDisconnect(onDisconnect);\n subscribed = true;\n }\n\n /** One attempt: open the socket, exchange hello/ack, replay the registry. */\n function performHandshake(): Promise<AppSession> {\n ensureSubscribed();\n return transport.connect().then(\n () =>\n new Promise<AppSession>((resolve, reject) => {\n if (closed) {\n reject(new Error(\"app closed during handshake\"));\n return;\n }\n let settled = false;\n const timer = setTimeout(() => {\n pending?.settle(new Error(`app handshake timed out after ${helloTimeoutMs}ms`));\n }, helloTimeoutMs);\n\n pending = {\n settle: (err, bound) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n pending = null;\n if (err || !bound) {\n reject(err ?? new Error(\"app handshake failed\"));\n return;\n }\n setSession(bound);\n logger.info(\"app connection established\", { appId, sessionId: bound.sessionId });\n // Replay the full surface — on a reconnect the gateway dropped our\n // caps when the previous connection closed.\n const wire = Array.from(registry.values()).map(toWire);\n if (wire.length > 0) {\n transport.send({ type: \"capability_register\", capabilities: wire });\n }\n resolve(bound);\n },\n };\n\n transport.send({ type: \"app_hello\", appId, protocolVersion });\n }),\n (err) => Promise.reject(err instanceof Error ? err : new Error(String(err))),\n );\n }\n\n const backoffMs = (attempt: number) =>\n Math.min(policy.maxMs, policy.baseMs * 2 ** Math.max(0, attempt - 1));\n\n /** Rebind after a dropped socket. Stops on a permanent refusal or close(). */\n async function reconnectLoop(): Promise<void> {\n const myEpoch = epoch;\n for (let attempt = 1; attempt <= policy.maxAttempts; attempt++) {\n const wait = backoffMs(attempt);\n logger.debug(\"scheduling app reconnect\", { appId, attempt, waitMs: wait });\n // close() cancels the timer, so it also has to hand us the resolver —\n // otherwise this await never settles and the loop frame parks forever,\n // pinning the transport and every other captured binding.\n await new Promise<void>((r) => {\n reconnectWake = r;\n reconnectTimer = setTimeout(r, wait);\n });\n reconnectWake = null;\n if (closed || epoch !== myEpoch) {\n logger.debug(\"app reconnect cancelled\", { appId, attempt });\n return;\n }\n try {\n await performHandshake();\n logger.info(\"app connection re-established\", { appId });\n return;\n } catch (err) {\n if (err instanceof AppHandshakeRefusedError && !err.retryable) {\n logger.error(\"app reconnect refused permanently\", { appId, error: err.message });\n return;\n }\n logger.warn(\"app reconnect attempt failed\", {\n appId,\n attempt,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n logger.error(\"app reconnect gave up\", { appId, attempts: policy.maxAttempts });\n }\n\n async function connect(): Promise<AppSession> {\n if (session) return session;\n if (connecting) throw new Error(\"app connect() is already in progress\");\n connecting = true;\n // A closed app is reusable: connect() reopens it. Any reconnect loop still\n // parked from the previous life is fenced off by the epoch close() bumped.\n closed = false;\n try {\n for (let attempt = 1; ; attempt++) {\n try {\n return await performHandshake();\n } catch (err) {\n // Only a *retryable* refusal is worth another attempt on the initial\n // connect: the app raced the session's agent container on boot. Every\n // other failure (bad token, timeout, transport error) is terminal here.\n const transient = err instanceof AppHandshakeRefusedError && err.retryable;\n if (!transient || !reconnectEnabled || attempt >= policy.maxAttempts || closed) {\n throw err;\n }\n logger.debug(\"app handshake refused (retryable), retrying\", { appId, attempt });\n await delay(backoffMs(attempt));\n }\n }\n } finally {\n connecting = false;\n }\n }\n\n async function close(): Promise<void> {\n closed = true;\n epoch++;\n clearTimeout(reconnectTimer);\n // Wake a loop parked on its backoff so it observes `closed` and returns,\n // rather than awaiting a timer that will now never fire.\n reconnectWake?.();\n reconnectWake = null;\n connecting = false;\n // Abort an in-flight handshake — rejects the pending connect and clears its\n // timer so nothing leaks past close().\n pending?.settle(new Error(\"app closed during handshake\"));\n unsubscribeMessage?.();\n unsubscribeDisconnect?.();\n unsubscribeMessage = null;\n unsubscribeDisconnect = null;\n subscribed = false;\n session = null;\n await transport.disconnect();\n }\n\n return {\n registerCapability,\n deregisterCapability,\n get capabilities() {\n return Array.from(registry.values()).map(toWire);\n },\n get session() {\n return session;\n },\n connect,\n onSessionChange(handler) {\n sessionListeners.add(handler);\n return () => sessionListeners.delete(handler);\n },\n close,\n };\n}\n","/**\n * Connection-param handshake between the embedding host and the app frame.\n *\n * An embedded app needs three things to open its back-connection: the gateway\n * URL, its own `appId`, and a session identity token. The token is a **secret**,\n * so it must not ride in the iframe `src` — the preview surface that serves the\n * app is unauthenticated, and a URL lands in access logs, `Referer` headers, and\n * browser history. Instead the host posts it over `postMessage`, and the app\n * asks for it with a ready ping once its script is running.\n *\n * The host cannot simply post on iframe `load`: it has no way to know when the\n * app's script is listening. So the app speaks first.\n *\n * ```text\n * app → host { type: 'skaile_app_ready', appId } targetOrigin = parentOrigin\n * host → app { type: 'skaile_app_params', appId, url, token } targetOrigin = appOrigin\n * ```\n *\n * Both sides pin the peer: the app checks `event.origin === parentOrigin` and\n * that the message came from its own parent; the host checks the origin and that\n * `event.source` is the very iframe it rendered. The app learns the expected\n * parent origin from a **non-secret** query param on its `src`\n * ({@link PARENT_ORIGIN_QUERY_PARAM}), which is safe to leak precisely because it\n * is not a credential.\n *\n * @category App SDK\n * @since 3.5.0\n */\n\n/**\n * Query param on the app's iframe `src` carrying the embedder's origin. Not a\n * secret — it is the value the app pins its `postMessage` peer to.\n */\nexport const PARENT_ORIGIN_QUERY_PARAM = \"skaileParentOrigin\";\n\n/** Default timeout waiting for the host's params reply. */\nexport const DEFAULT_PARAMS_TIMEOUT_MS = 10_000;\n\n/** App → host. Announces the app frame is listening and wants its params. */\nexport type SkaileAppReadyMessage = {\n type: \"skaile_app_ready\";\n /** The app asking. The host answers only for an app it actually embedded. */\n appId: string;\n};\n\n/** Host → app. Carries the gateway URL and the single-use session identity token. */\nexport type SkaileAppParamsMessage = {\n type: \"skaile_app_params\";\n /** Echoed back so a frame embedding several apps can disambiguate. */\n appId: string;\n /** The gateway app-channel URL, e.g. `wss://platform/app-connect`. */\n url: string;\n /** Single-use session identity token. Consumed by the gateway at `app_hello`. */\n token: string;\n};\n\n/** Resolved params, ready to hand to `createWebSocketAppTransport` + `createSkaileApp`. */\nexport type AppConnectionParams = {\n url: string;\n appId: string;\n token: string;\n};\n\n/** The subset of `postMessage` targets this module needs. */\nexport type PostMessageTarget = {\n postMessage(message: unknown, targetOrigin: string): void;\n};\n\n/** Structural view of a `MessageEvent`, so tests need no DOM. */\nexport type MessageEventLike = {\n readonly origin: string;\n readonly source: unknown;\n readonly data: unknown;\n};\n\n/** Structural view of the `window` bits this module needs. Injectable for tests. */\nexport type AppWindowLike = {\n readonly parent: PostMessageTarget | null;\n readonly location: { readonly search: string };\n addEventListener(type: \"message\", handler: (event: MessageEventLike) => void): void;\n removeEventListener(type: \"message\", handler: (event: MessageEventLike) => void): void;\n};\n\n/**\n * Options for {@link resolveAppConnectionParams}.\n *\n * @category App SDK\n * @since 3.5.0\n */\nexport interface ResolveAppConnectionParamsOptions {\n /** The embedded app's stable id. Must match the params the host replies with. */\n appId: string;\n /**\n * Expected origin of the embedding host. Defaults to the\n * {@link PARENT_ORIGIN_QUERY_PARAM} query param on the current URL. Never `\"*\"`\n * or `\"null\"` — an unpinned peer defeats the point of not putting the token in\n * the URL.\n */\n parentOrigin?: string;\n /** How long to wait for the host's reply. Defaults to {@link DEFAULT_PARAMS_TIMEOUT_MS}. */\n timeoutMs?: number;\n /** Window to listen and post on. Defaults to the global. Injectable for tests. */\n windowRef?: AppWindowLike;\n}\n\nfunction readParentOriginFromQuery(search: string): string | null {\n const value = new URLSearchParams(search).get(PARENT_ORIGIN_QUERY_PARAM);\n return value && value.length > 0 ? value : null;\n}\n\nfunction isParamsMessage(data: unknown, appId: string): data is SkaileAppParamsMessage {\n if (!data || typeof data !== \"object\") return false;\n const m = data as Partial<SkaileAppParamsMessage>;\n // Trim before the emptiness check: a whitespace-only token would survive to\n // the gateway and be refused there. Failing here is the clearer error.\n return (\n m.type === \"skaile_app_params\" &&\n m.appId === appId &&\n typeof m.url === \"string\" &&\n m.url.trim().length > 0 &&\n typeof m.token === \"string\" &&\n m.token.trim().length > 0\n );\n}\n\n/**\n * Ask the embedding host for this app's connection params.\n *\n * Posts a {@link SkaileAppReadyMessage} to the parent and resolves with the\n * matching {@link SkaileAppParamsMessage}. Messages from any other origin, from\n * any window other than the parent, or for another `appId` are ignored — not\n * rejected — because a page may legitimately receive unrelated `postMessage`\n * traffic.\n *\n * Call it again to re-request a fresh token: the token is single-use, so a\n * reconnecting transport needs a new one (see `createSkaileApp`'s `reconnect`).\n *\n * @throws when not embedded, when the parent origin cannot be determined or is\n * unpinnable, or when the host does not reply within `timeoutMs`.\n *\n * @category App SDK\n * @since 3.5.0\n */\nexport function resolveAppConnectionParams(\n options: ResolveAppConnectionParamsOptions,\n): Promise<AppConnectionParams> {\n const { appId, timeoutMs = DEFAULT_PARAMS_TIMEOUT_MS } = options;\n const win =\n options.windowRef ?? (globalThis as unknown as { window?: AppWindowLike }).window ?? null;\n if (!win) {\n return Promise.reject(\n new Error(\"resolveAppConnectionParams requires a window (pass `windowRef` off-browser)\"),\n );\n }\n\n const parent = win.parent;\n // A top-level page's `parent` is itself. An app that is not embedded has no\n // host to ask, and posting to itself would hang until the timeout.\n if (!parent || (parent as unknown) === (win as unknown)) {\n return Promise.reject(\n new Error(\"app is not embedded — no parent frame to request params from\"),\n );\n }\n\n const parentOrigin = options.parentOrigin ?? readParentOriginFromQuery(win.location.search);\n if (!parentOrigin) {\n return Promise.reject(\n new Error(\n `cannot determine the host origin: pass \\`parentOrigin\\` or set the \\`${PARENT_ORIGIN_QUERY_PARAM}\\` query param`,\n ),\n );\n }\n // `*` would broadcast the ready ping to whatever is above us, and `null` (an\n // opaque, sandboxed origin) cannot be pinned at all. Both make the peer check\n // meaningless, and the token would then be posted to an unverified window.\n if (parentOrigin === \"*\" || parentOrigin === \"null\") {\n return Promise.reject(new Error(`refusing to use an unpinnable host origin: ${parentOrigin}`));\n }\n\n return new Promise<AppConnectionParams>((resolve, reject) => {\n let settled = false;\n\n const cleanup = () => {\n clearTimeout(timer);\n win.removeEventListener(\"message\", onMessage);\n };\n\n const timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n cleanup();\n reject(\n new Error(`timed out after ${timeoutMs}ms waiting for connection params from the host`),\n );\n }, timeoutMs);\n\n function onMessage(event: MessageEventLike): void {\n if (settled) return;\n // Pin the peer on both axes. Origin alone is not enough: any frame served\n // from the host's origin could otherwise answer.\n if (event.origin !== parentOrigin || event.source !== parent) return;\n if (!isParamsMessage(event.data, appId)) return;\n settled = true;\n cleanup();\n resolve({ url: event.data.url, appId, token: event.data.token });\n }\n\n win.addEventListener(\"message\", onMessage);\n\n const ready: SkaileAppReadyMessage = { type: \"skaile_app_ready\", appId };\n try {\n parent.postMessage(ready, parentOrigin);\n } catch (err) {\n settled = true;\n cleanup();\n reject(err instanceof Error ? err : new Error(String(err)));\n }\n });\n}\n","/**\n * `defineAppCapability` — author-friendly factory for an embedded app's\n * agent-controllable capabilities.\n *\n * It mirrors the runner's `defineCapability` (`@skaile/workspaces/runner`) but\n * is scoped to the app side: the handler always runs *in the app*, so `side` is\n * fixed to `'app'` and the author never passes it. The originating `appId` is\n * known to the SDK, not the individual capability, so `origin` is stamped by\n * {@link createSkaileApp} at registration time — not here.\n *\n * @category App SDK\n * @since 3.4.0\n */\n\nimport type { CapabilityAudience, CapabilityOrigin, RenderSpec } from \"@skaile/workspaces/types\";\nimport * as z from \"zod\";\nimport type { AppLogger } from \"./logger.js\";\n\n/**\n * Per-invocation context passed to an app capability handler. Analogous to the\n * runner's `HandlerContext`, but carries the `appId` and the SDK's\n * {@link AppLogger} (there is no server-side `capability:<origin>:<name>` log\n * slice inside the app process).\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport type AppHandlerContext = {\n /** The session this app connection is bound to (from the hello ack). */\n sessionId: string;\n /** The embedded app's id. */\n appId: string;\n /** Logger to emit through; defaults to the SDK's configured logger. */\n log: AppLogger;\n};\n\n/**\n * Author-facing arguments to {@link defineAppCapability}. Mirrors the wire\n * {@link import(\"@skaile/workspaces/types\").Capability} fields an app may set,\n * minus `side` / `origin` (fixed / stamped by the SDK) and the runtime-only\n * fields that only make sense platform-side.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport type DefineAppCapabilityArgs<\n T extends z.ZodTypeAny,\n R extends z.ZodTypeAny | undefined = undefined,\n> = {\n /**\n * Stable, session-unique name. Convention: `app.<sector>.<verb>` — e.g.\n * `app.ui.switch_contract` (UI sector) or `app.backend.insert_contract`\n * (backend sector). v1 distinguishes sectors by this naming convention.\n */\n name: string;\n /** Human-readable label for user-facing surfaces; falls back to `name`. */\n displayName?: string;\n /** Human/LLM-readable summary; shown to the agent at tool registration. */\n description: string;\n /** Zod schema validating the invocation arguments. Converted to JSON Schema. */\n input: T;\n /** Optional Zod schema for the return value. */\n output?: R;\n /** Lifetime: `session` survives the whole session, `turn` is dropped at end-of-turn. */\n scope?: \"session\" | \"turn\";\n /**\n * When true, the platform shows a confirm UI and waits for user approval\n * before the invocation is forwarded to the app. Mark every mutation\n * (backend writes, destructive UI actions) with this.\n */\n requiresApproval?: boolean;\n /** Documentation hint: side-effect, render, or pure query. */\n kind?: \"effect\" | \"render\" | \"query\";\n /**\n * Audience scoping. Omit for the default (`['llm','user']`). App capabilities\n * default to LLM-only in the command palette (`isUserInvokable`); set\n * `['llm','user']` on the user-facing actions you want to appear in cmdK.\n */\n audience?: CapabilityAudience[];\n /** Appended to the system prompt's `<CAPABILITIES>` section. */\n promptFragment?: string;\n /** Per-capability override for the platform's result round-trip timeout. */\n callTimeoutMs?: number;\n /** Render contract for capabilities that produce a UI surface. */\n render?: RenderSpec;\n /** The handler — runs in the app. Receives validated input + {@link AppHandlerContext}. */\n handler: (\n args: z.infer<T>,\n ctx: AppHandlerContext,\n ) => Promise<R extends z.ZodTypeAny ? z.infer<R> : unknown>;\n};\n\n/**\n * A defined app capability: the wire-format fields the SDK registers (minus the\n * SDK-stamped `origin`), plus the Zod schemas and type-erased handler used for\n * runtime input validation and inbound dispatch.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport type DefinedAppCapability = {\n name: string;\n displayName?: string;\n description: string;\n inputSchema: Record<string, unknown>;\n outputSchema?: Record<string, unknown>;\n /** Always `'app'` — the handler runs in the embedded app. */\n side: \"app\";\n scope?: \"session\" | \"turn\";\n requiresApproval?: boolean;\n kind?: \"effect\" | \"render\" | \"query\";\n audience?: CapabilityAudience[];\n promptFragment?: string;\n callTimeoutMs?: number;\n render?: RenderSpec;\n /** Original Zod schema for runtime input validation. Not serialized to the wire. */\n readonly inputZod: z.ZodTypeAny;\n /** Original Zod schema for the optional output, if declared. Not serialized. */\n readonly outputZod?: z.ZodTypeAny;\n /** Type-erased handler dispatched on an inbound `capability_invoked`. */\n handler: (args: unknown, ctx: AppHandlerContext) => Promise<unknown>;\n};\n\n/** Origin kind an app capability always carries once stamped by the SDK. */\nexport type AppCapabilityOrigin = Extract<CapabilityOrigin, { kind: \"app\" }>;\n\n/**\n * Build a {@link DefinedAppCapability} from author-friendly Zod-typed args.\n * `side` is fixed to `'app'`; `origin` is added by the SDK at registration\n * time (it needs the SDK's `appId`).\n *\n * @example\n * ```ts\n * const switchContract = defineAppCapability({\n * name: 'app.ui.switch_contract',\n * description: 'Switch the visible contract in the app UI.',\n * input: z.object({ contractId: z.string() }),\n * handler: async ({ contractId }, ctx) => {\n * ctx.log.info('switching contract', { contractId });\n * ui.select(contractId);\n * },\n * });\n * ```\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport function defineAppCapability<\n T extends z.ZodTypeAny,\n R extends z.ZodTypeAny | undefined = undefined,\n>(args: DefineAppCapabilityArgs<T, R>): DefinedAppCapability {\n const inputSchema = z.toJSONSchema(args.input, {\n target: \"draft-07\",\n io: \"input\",\n }) as Record<string, unknown>;\n const outputSchema = args.output\n ? (z.toJSONSchema(args.output, {\n target: \"draft-07\",\n io: \"output\",\n }) as Record<string, unknown>)\n : undefined;\n\n return {\n name: args.name,\n displayName: args.displayName,\n description: args.description,\n inputSchema,\n outputSchema,\n side: \"app\",\n scope: args.scope,\n requiresApproval: args.requiresApproval,\n kind: args.kind,\n audience: args.audience,\n promptFragment: args.promptFragment,\n callTimeoutMs: args.callTimeoutMs,\n render: args.render,\n inputZod: args.input,\n outputZod: args.output,\n handler: args.handler as (args: unknown, ctx: AppHandlerContext) => Promise<unknown>,\n };\n}\n","/**\n * WebSocket-backed {@link AppTransport}.\n *\n * Browser-safe: uses the standard `WebSocket` global (no `ws` dependency, no\n * `node:*` imports), so it runs unchanged in an embedded app's iframe and in a\n * lightweight Node 22+ service. The injected session identity is presented as a\n * bearer **subprotocol** — the same `skaile-bearer.<base64url>` scheme the\n * platform gateway already decodes for runner connections — so the gateway\n * verifies identity at the WS upgrade, before any `app_hello` is processed.\n *\n * Frames are bare JSON of the app-channel messages (no `MessageV2` envelope),\n * matching the runner peer's on-the-wire convention.\n *\n * @category App SDK\n * @since 3.4.0\n */\n\nimport type { AppClientMessage, AppServerMessage, AppTransport } from \"./transport.js\";\n\n/** Matches `BEARER_SUBPROTOCOL_PREFIX` in `@skaile/workspaces/transport`'s ws/auth. */\nconst BEARER_SUBPROTOCOL_PREFIX = \"skaile-bearer.\";\n\n/** Encode a bearer token as a WS subprotocol token (base64url, no padding). */\nfunction encodeBearerSubprotocol(token: string): string {\n // Encode via UTF-8 bytes so a non-ASCII token doesn't make `btoa` throw\n // (btoa is Latin-1 only). The gateway decodes the mirror of this.\n const binary = String.fromCharCode(...new TextEncoder().encode(token));\n const b64 = btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n return BEARER_SUBPROTOCOL_PREFIX + b64;\n}\n\n/** Minimal structural view of the parts of the `WebSocket` API the transport uses. */\ntype WebSocketLike = {\n readonly readyState: number;\n send(data: string): void;\n close(code?: number, reason?: string): void;\n addEventListener(type: \"open\", handler: () => void): void;\n addEventListener(type: \"close\", handler: () => void): void;\n addEventListener(type: \"error\", handler: () => void): void;\n addEventListener(type: \"message\", handler: (ev: { data: unknown }) => void): void;\n};\n\n/** Constructor shape for a `WebSocket` implementation (global or injected). */\nexport type WebSocketFactory = new (url: string, protocols?: string | string[]) => WebSocketLike;\n\n/**\n * Supplies a session identity token for one connection attempt.\n *\n * The gateway's token is **single-use** — it is consumed at `app_hello` — so a\n * reconnecting transport cannot replay the one it opened with. Pass a provider\n * (typically a call to `resolveAppConnectionParams`) and every `connect()` mints\n * a fresh token.\n *\n * @category App SDK\n * @since 3.5.0\n */\nexport type SessionTokenProvider = () => string | Promise<string>;\n\n/**\n * Options for {@link createWebSocketAppTransport}.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport interface WebSocketAppTransportOptions {\n /** The gateway app-channel URL (e.g. `wss://platform/app-connect`). */\n url: string;\n /**\n * The injected session identity token, or a {@link SessionTokenProvider} that\n * yields a fresh one per connection attempt. Presented as the WS bearer\n * subprotocol; the gateway verifies it and resolves the session/user.\n *\n * A bare string cannot survive a reconnect (the gateway consumes the token at\n * `app_hello`) — pass a provider whenever `reconnect` is enabled on the peer.\n */\n sessionToken: string | SessionTokenProvider;\n /**\n * WebSocket implementation. Defaults to the `WebSocket` global (browser /\n * Node 22+). Injectable for tests or non-standard runtimes.\n */\n WebSocketImpl?: WebSocketFactory;\n /** Connect timeout in ms. Default 10000. */\n connectTimeoutMs?: number;\n}\n\nconst OPEN = 1;\n\n/**\n * Create a WebSocket-backed {@link AppTransport}. The session token is sent as a\n * bearer subprotocol at connect time; app-channel messages are JSON-framed.\n *\n * @category App SDK\n * @since 3.4.0\n */\nexport function createWebSocketAppTransport(options: WebSocketAppTransportOptions): AppTransport {\n const { url, sessionToken, connectTimeoutMs = 10_000 } = options;\n const resolved =\n options.WebSocketImpl ?? (globalThis as { WebSocket?: WebSocketFactory }).WebSocket;\n if (!resolved) {\n throw new Error(\n \"No WebSocket implementation available. Pass `WebSocketImpl` in a runtime without a global WebSocket.\",\n );\n }\n const WebSocketImpl: WebSocketFactory = resolved;\n\n let ws: WebSocketLike | null = null;\n const messageHandlers = new Set<(m: AppServerMessage) => void>();\n const disconnectHandlers = new Set<() => void>();\n\n function connect(): Promise<void> {\n // A provider is resolved per attempt — the gateway consumes the token at\n // `app_hello`, so a reconnect must present a new one. A bare string opens the\n // socket synchronously, so `connect()` still constructs it before it yields.\n if (typeof sessionToken === \"function\") {\n return Promise.resolve(sessionToken()).then(openSocket);\n }\n return openSocket(sessionToken);\n }\n\n function openSocket(token: string): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n let opened = false;\n const socket = new WebSocketImpl(url, [encodeBearerSubprotocol(token)]);\n ws = socket;\n\n const timer = setTimeout(() => {\n if (opened) return;\n socket.close(4008, \"connect timeout\");\n reject(new Error(`app transport connect timed out after ${connectTimeoutMs}ms`));\n }, connectTimeoutMs);\n\n socket.addEventListener(\"open\", () => {\n opened = true;\n clearTimeout(timer);\n resolve();\n });\n socket.addEventListener(\"error\", () => {\n if (!opened) {\n clearTimeout(timer);\n reject(new Error(\"app transport connection error\"));\n }\n });\n socket.addEventListener(\"close\", () => {\n clearTimeout(timer);\n if (ws === socket) ws = null;\n for (const h of disconnectHandlers) h();\n });\n socket.addEventListener(\"message\", (ev) => {\n let parsed: AppServerMessage;\n try {\n parsed = JSON.parse(String(ev.data)) as AppServerMessage;\n } catch {\n return;\n }\n for (const h of messageHandlers) h(parsed);\n });\n });\n }\n\n return {\n connect,\n async disconnect() {\n const socket = ws;\n ws = null;\n socket?.close(1000, \"client closed\");\n },\n send(message: AppClientMessage) {\n if (!ws || ws.readyState !== OPEN) {\n throw new Error(\"app transport is not connected\");\n }\n ws.send(JSON.stringify(message));\n },\n onMessage(handler) {\n messageHandlers.add(handler);\n return () => messageHandlers.delete(handler);\n },\n onDisconnect(handler) {\n disconnectHandlers.add(handler);\n return () => disconnectHandlers.delete(handler);\n },\n get connected() {\n return ws?.readyState === OPEN;\n },\n };\n}\n"]}
|