@wakeshift/sdk 0.2.0 → 0.4.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 +182 -0
- package/README.md +81 -64
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/events.d.ts +92 -48
- package/dist/events.js +217 -76
- package/dist/events.js.map +1 -1
- package/dist/harness.js +2 -2
- package/dist/harness.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/key.d.ts +1 -6
- package/dist/key.js +9 -5
- package/dist/key.js.map +1 -1
- package/dist/retry.js +1 -1
- package/dist/retry.js.map +1 -1
- package/dist/spec.d.ts +2 -2
- package/dist/spec.js +2 -2
- package/dist/spec.js.map +1 -1
- package/dist/toolcall.d.ts +154 -0
- package/dist/toolcall.js +558 -0
- package/dist/toolcall.js.map +1 -0
- package/dist/wakeshift.d.ts +75 -241
- package/dist/wakeshift.js +157 -247
- package/dist/wakeshift.js.map +1 -1
- package/package.json +17 -3
- package/dist/guest.d.ts +0 -119
- package/dist/guest.js +0 -506
- package/dist/guest.js.map +0 -1
- package/dist/limits.d.ts +0 -19
- package/dist/limits.js +0 -105
- package/dist/limits.js.map +0 -1
- package/dist/sealed.d.ts +0 -4
- package/dist/sealed.js +0 -38
- package/dist/sealed.js.map +0 -1
package/dist/wakeshift.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
1
|
import { WakeshiftError } from "./errors.js";
|
|
3
2
|
import { WakeEvent, attachEvents } from "./events.js";
|
|
4
|
-
import { parseKey, signAssertion } from "./key.js";
|
|
3
|
+
import { parseKey, signAssertion, isTestKey } from "./key.js";
|
|
5
4
|
import { retryRefused } from "./retry.js";
|
|
6
5
|
/** Wakeshift's production core, the default for every key but an older wst_test_ one. Override with `core` or WAKESHIFT_CORE. */
|
|
7
6
|
export const DEFAULT_CORE = "https://app.wakeshift.ai";
|
|
@@ -14,19 +13,25 @@ export const HARNESS_ARG = "harness";
|
|
|
14
13
|
* when its program gives it one (Cursor and Grok Bot: CURSOR_CONVERSATION_ID).
|
|
15
14
|
*/
|
|
16
15
|
export const CONVERSATION_ARG = "conversation_id";
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
16
|
+
// recipientsOf reads an emit's unmatched or ended list: "user",
|
|
17
|
+
// { native: user } or { user, subscription }.
|
|
18
|
+
function recipientsOf(raw) {
|
|
19
|
+
if (!Array.isArray(raw))
|
|
20
|
+
return [];
|
|
21
|
+
const out = [];
|
|
22
|
+
for (const r of raw) {
|
|
23
|
+
if (typeof r === "string")
|
|
24
|
+
out.push(r);
|
|
25
|
+
else if (r && typeof r === "object") {
|
|
26
|
+
const m = r;
|
|
27
|
+
if (typeof m.native === "string" && m.native)
|
|
28
|
+
out.push({ native: m.native });
|
|
29
|
+
else if (typeof m.user === "string")
|
|
30
|
+
out.push({ user: m.user, subscription: typeof m.subscription === "string" ? m.subscription : "" });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
30
35
|
// userAgentOf is the User-Agent of the HTTP request a tool call came in
|
|
31
36
|
// ("" if there's none, e.g. over stdio).
|
|
32
37
|
function userAgentOf(extra) {
|
|
@@ -36,45 +41,51 @@ function userAgentOf(extra) {
|
|
|
36
41
|
const v = headers["user-agent"] ?? headers["User-Agent"];
|
|
37
42
|
return (Array.isArray(v) ? v[0] : v) ?? "";
|
|
38
43
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
out =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
out._meta = { ...(out._meta ?? {}), ...a.meta };
|
|
44
|
+
/** @internal Packs a tool call as Wakeshift takes it: empty fields left out, the agent's claims trimmed. */
|
|
45
|
+
export function packCall(c) {
|
|
46
|
+
const out = { client: c.clientName ?? "" };
|
|
47
|
+
if (c.user)
|
|
48
|
+
out.user = c.user;
|
|
49
|
+
if (c.meta && Object.keys(c.meta).length > 0)
|
|
50
|
+
out.meta = c.meta;
|
|
51
|
+
if (c.session)
|
|
52
|
+
out.session = c.session;
|
|
53
|
+
if (c.userAgent)
|
|
54
|
+
out.user_agent = c.userAgent;
|
|
55
|
+
if (c.harness?.trim())
|
|
56
|
+
out.harness = c.harness.trim();
|
|
57
|
+
if (c.conversation?.trim())
|
|
58
|
+
out.conversation = c.conversation.trim();
|
|
59
|
+
if (c.tool?.trim())
|
|
60
|
+
out.tool = c.tool.trim();
|
|
57
61
|
return out;
|
|
58
62
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
/**
|
|
64
|
+
* @internal The call to one of attach's own tools (subscribe_events and the
|
|
65
|
+
* others): its _meta and User-Agent, the client's name, the user, and
|
|
66
|
+
* session_key, harness and conversation_id from its arguments.
|
|
67
|
+
*/
|
|
68
|
+
export function toolCallOf(extra, client, user, args, tool) {
|
|
69
|
+
return packCall({
|
|
70
|
+
meta: extra?._meta,
|
|
71
|
+
clientName: client,
|
|
72
|
+
user,
|
|
73
|
+
session: str(args, SESSION_ARG),
|
|
74
|
+
userAgent: userAgentOf(extra),
|
|
75
|
+
harness: str(args, HARNESS_ARG),
|
|
76
|
+
conversation: str(args, CONVERSATION_ARG),
|
|
77
|
+
tool,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
64
80
|
// str is a tool argument's value if it's a string.
|
|
65
81
|
function str(args, name) {
|
|
66
82
|
return typeof args?.[name] === "string" ? args[name] : undefined;
|
|
67
83
|
}
|
|
68
84
|
/**
|
|
69
|
-
* The Wakeshift client for an app:
|
|
70
|
-
*
|
|
85
|
+
* The Wakeshift client for an app: define the events your app offers,
|
|
86
|
+
* attach them to your MCP server, and emit them when they happen.
|
|
71
87
|
*/
|
|
72
88
|
export class Wakeshift {
|
|
73
|
-
/**
|
|
74
|
-
* An older key's mode (wst_test_…, wst_live_…); null for a current key.
|
|
75
|
-
* @deprecated Modes mean nothing now, and current keys have none.
|
|
76
|
-
*/
|
|
77
|
-
mode;
|
|
78
89
|
core;
|
|
79
90
|
key;
|
|
80
91
|
// The app's ID: from the key, if it names the app, or else from app().
|
|
@@ -89,10 +100,9 @@ export class Wakeshift {
|
|
|
89
100
|
throw new Error("wakeshift: no app key (WAKESHIFT_KEY?)");
|
|
90
101
|
this.key = parseKey(opts.key);
|
|
91
102
|
this.knownAppId = this.key.appId;
|
|
92
|
-
this.mode = this.key.mode;
|
|
93
103
|
// Test keys come from staging and local consoles, and one sent to
|
|
94
104
|
// production fails in a way that's hard to read: ask for the core.
|
|
95
|
-
const core = opts.core || process.env.WAKESHIFT_CORE || (
|
|
105
|
+
const core = opts.core || process.env.WAKESHIFT_CORE || (isTestKey(opts.key) ? "" : DEFAULT_CORE);
|
|
96
106
|
if (!core) {
|
|
97
107
|
throw new Error("wakeshift: no core URL for this test key: set WAKESHIFT_CORE, or pass { core }, to the address of the console the key came from (for example https://app.staging.wakeshift.ai)");
|
|
98
108
|
}
|
|
@@ -113,7 +123,8 @@ export class Wakeshift {
|
|
|
113
123
|
return this.knownAppId;
|
|
114
124
|
}
|
|
115
125
|
/**
|
|
116
|
-
* Which app the key is, as Wakeshift says: its ID and name
|
|
126
|
+
* Which app the key is, as Wakeshift says: its ID and name, and the
|
|
127
|
+
* key's. Asks once
|
|
117
128
|
* (GET /api/apps/v1/app) and remembers the answer; afterwards
|
|
118
129
|
* `wake.appId` works for any key.
|
|
119
130
|
*/
|
|
@@ -125,7 +136,8 @@ export class Wakeshift {
|
|
|
125
136
|
}
|
|
126
137
|
const id = this.knownAppId ?? r.id;
|
|
127
138
|
this.knownAppId = id;
|
|
128
|
-
|
|
139
|
+
const str = (v) => (typeof v === "string" ? v : "");
|
|
140
|
+
return { id, name: str(r.name), key: { id: str(r.key?.key_id), name: str(r.key?.name) } };
|
|
129
141
|
})
|
|
130
142
|
.catch((err) => {
|
|
131
143
|
this.appInfo = undefined; // a later call asks again
|
|
@@ -145,202 +157,103 @@ export class Wakeshift {
|
|
|
145
157
|
warn(message) {
|
|
146
158
|
this.warnFn(message);
|
|
147
159
|
}
|
|
148
|
-
/** Reports a warning, once per kind. */
|
|
160
|
+
/** @internal Reports a warning, once per kind. */
|
|
149
161
|
warnOnce(kind, message) {
|
|
150
162
|
if (this.warned.has(kind))
|
|
151
163
|
return;
|
|
152
164
|
this.warned.add(kind);
|
|
153
165
|
this.warnFn(message);
|
|
154
166
|
}
|
|
155
|
-
/**
|
|
156
|
-
* The arguments the agent passes Wakeshift's part of the call in, as a
|
|
157
|
-
* zod shape for your subscribe tool's input (and any other tool whose
|
|
158
|
-
* results you annotate): `inputSchema: { repo: z.string(), ...wake.args }`.
|
|
159
|
-
* session_key (the key a result gives the conversation), harness (the
|
|
160
|
-
* program the agent runs in) and conversation_id (its own ID for the
|
|
161
|
-
* conversation, where its program gives one). Wakeshift adds nothing to
|
|
162
|
-
* tools you don't spread it into.
|
|
163
|
-
*/
|
|
164
|
-
get args() {
|
|
165
|
-
return argsShape;
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Packs the tool call from an MCP tool callback's `extra`: its _meta and
|
|
169
|
-
* User-Agent, the client's name (from `server`, or pass `client`), the
|
|
170
|
-
* user, and session_key, harness and conversation_id from `args`.
|
|
171
|
-
*/
|
|
172
|
-
call(extra, opts = {}) {
|
|
173
|
-
let client = opts.client;
|
|
174
|
-
if (client === undefined) {
|
|
175
|
-
if (!opts.server)
|
|
176
|
-
throw new Error("wakeshift: wake.call needs the MCP client's name: pass { server } (your McpServer), or { client }");
|
|
177
|
-
client = clientNameOf(opts.server);
|
|
178
|
-
}
|
|
179
|
-
const args = opts.args;
|
|
180
|
-
return this.callOf({
|
|
181
|
-
meta: extra?._meta,
|
|
182
|
-
clientName: client,
|
|
183
|
-
user: opts.user,
|
|
184
|
-
session: opts.session ?? str(args, SESSION_ARG),
|
|
185
|
-
userAgent: userAgentOf(extra),
|
|
186
|
-
harness: opts.harness ?? str(args, HARNESS_ARG),
|
|
187
|
-
conversation: opts.conversation ?? str(args, CONVERSATION_ARG),
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
/** Packs a tool call without an MCP framework. */
|
|
191
|
-
callOf(c) {
|
|
192
|
-
const out = { client: c.clientName ?? "" };
|
|
193
|
-
if (c.user)
|
|
194
|
-
out.user = c.user;
|
|
195
|
-
if (c.meta && Object.keys(c.meta).length > 0)
|
|
196
|
-
out.meta = c.meta;
|
|
197
|
-
if (c.session)
|
|
198
|
-
out.session = c.session;
|
|
199
|
-
if (c.userAgent)
|
|
200
|
-
out.user_agent = c.userAgent;
|
|
201
|
-
if (c.harness?.trim())
|
|
202
|
-
out.harness = c.harness.trim();
|
|
203
|
-
if (c.conversation?.trim())
|
|
204
|
-
out.conversation = c.conversation.trim();
|
|
205
|
-
return out;
|
|
206
|
-
}
|
|
207
|
-
// recognised reads what the core recognised the caller as, and warns
|
|
208
|
-
// once if it was nothing and the agent didn't say (an older core that
|
|
209
|
-
// doesn't say: no warning).
|
|
210
|
-
recognised(agent) {
|
|
211
|
-
if (agent && !agent.harness && !agent.claimed)
|
|
212
|
-
this.warnOnce("unknown_client", UNKNOWN_CLIENT_WARNING);
|
|
213
|
-
return { harness: agent?.harness ?? null, via: agent?.via ?? null, claimed: agent?.claimed ?? null };
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Subscribes the calling conversation to topic (again: refreshes it).
|
|
217
|
-
* Check the caller may see the topic first: Wakeshift only refuses another
|
|
218
|
-
* user's u/<user>/… topics.
|
|
219
|
-
*/
|
|
220
|
-
async subscribe(call, topic, opts = {}) {
|
|
221
|
-
const body = { call, topic, once: !!opts.once };
|
|
222
|
-
if (opts.filter && Object.keys(opts.filter).length > 0)
|
|
223
|
-
body.filter = opts.filter;
|
|
224
|
-
if (opts.replay !== undefined)
|
|
225
|
-
body.replay = opts.replay;
|
|
226
|
-
if (opts.ttl !== undefined)
|
|
227
|
-
body.ttl = opts.ttl;
|
|
228
|
-
const out = await this.request("POST", "/topics/subscribe", body);
|
|
229
|
-
const agent = this.recognised(out.agent);
|
|
230
|
-
const addition = out.addition ?? {};
|
|
231
|
-
const s = out.subscription;
|
|
232
|
-
return {
|
|
233
|
-
id: s.id,
|
|
234
|
-
topic: s.topic,
|
|
235
|
-
filter: s.filter,
|
|
236
|
-
once: s.once,
|
|
237
|
-
expiresAt: new Date(s.expires_at),
|
|
238
|
-
session: out.session,
|
|
239
|
-
addition,
|
|
240
|
-
agent,
|
|
241
|
-
result: (text) => applyAddition(addition, text),
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
/** Ends the conversation's subscription to topic, or to all the app's topics. Returns how many ended. */
|
|
245
|
-
async unsubscribe(call, topic) {
|
|
246
|
-
const out = await this.request("POST", "/topics/unsubscribe", topic ? { call, topic } : { call });
|
|
247
|
-
return out.unsubscribed;
|
|
248
|
-
}
|
|
249
|
-
/** Wakes every conversation subscribed to one of topics whose filter the event passes (each once). */
|
|
250
|
-
async publish(topics, event) {
|
|
251
|
-
const out = await this.request("POST", "/topics/publish", { topics: typeof topics === "string" ? [topics] : topics, event });
|
|
252
|
-
return { matched: out.matched, duplicate: !!out.duplicate };
|
|
253
|
-
}
|
|
254
|
-
/** Wakes exactly one subscription's conversation, whatever its filter. */
|
|
255
|
-
async send(subscriptionId, event) {
|
|
256
|
-
const out = await this.request("POST", `/subscriptions/${encodeURIComponent(subscriptionId)}/send`, { event });
|
|
257
|
-
return out.status;
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* How many conversations are subscribed to topic, and how many are
|
|
261
|
-
* connected now, and how. Only reads: nothing is delivered, minted or ended.
|
|
262
|
-
*/
|
|
263
|
-
async topicStatus(topic) {
|
|
264
|
-
const out = await this.request("GET", `/topics/status?topic=${encodeURIComponent(topic)}`);
|
|
265
|
-
return { topic: out.topic, subscriptions: out.subscriptions, connected: out.connected, via: { stream: 0, poll: 0, hooks: 0, webhook: 0, ...out.via } };
|
|
266
|
-
}
|
|
267
|
-
/** Whether a subscription is active and its conversation connected now, and how. Only reads. */
|
|
268
|
-
async subscriptionStatus(subscriptionId) {
|
|
269
|
-
const out = await this.request("GET", `/subscriptions/${encodeURIComponent(subscriptionId)}`);
|
|
270
|
-
const s = { id: out.id, topic: out.topic, status: out.status, connected: out.connected };
|
|
271
|
-
if (out.via)
|
|
272
|
-
s.via = out.via;
|
|
273
|
-
if (out.expires_at)
|
|
274
|
-
s.expiresAt = new Date(out.expires_at);
|
|
275
|
-
return s;
|
|
276
|
-
}
|
|
277
|
-
/** A capability URL that publishes to topic: a POST's body becomes an event. */
|
|
278
|
-
async publishUrl(topic) {
|
|
279
|
-
return this.request("POST", "/topics/publish-urls", { topic });
|
|
280
|
-
}
|
|
281
|
-
/** Stops a publish URL working, a topic's or an event's (410 from then on). */
|
|
167
|
+
/** Stops a publish URL working (410 from then on). */
|
|
282
168
|
async revokePublishUrl(id) {
|
|
283
169
|
await this.request("DELETE", `/events/publish-urls/${encodeURIComponent(id)}`);
|
|
284
170
|
}
|
|
285
|
-
/** What Wakeshift would add to any other tool result for this conversation (creates nothing). */
|
|
286
|
-
async addition(call) {
|
|
287
|
-
const out = await this.request("POST", "/topics/annotate", { call });
|
|
288
|
-
return { ...out, agent: this.recognised(out.agent) };
|
|
289
|
-
}
|
|
290
|
-
/**
|
|
291
|
-
* Adds what Wakeshift has for the calling conversation to a tool result
|
|
292
|
-
* of yours (a copy): what's waiting for a harness that can't be woken, a
|
|
293
|
-
* fresh connector beacon while a Codex thread's connector isn't running.
|
|
294
|
-
* Only results you pass here (and a subscription's result()) change.
|
|
295
|
-
*/
|
|
296
|
-
async annotate(call, result) {
|
|
297
|
-
return applyAddition(await this.addition(call), result);
|
|
298
|
-
}
|
|
299
|
-
/** Ends every subscription to topic; each conversation is told. Returns how many ended. */
|
|
300
|
-
async closeTopic(topic) {
|
|
301
|
-
return (await this.request("POST", "/topics/close", { topic })).closed;
|
|
302
|
-
}
|
|
303
|
-
/**
|
|
304
|
-
* Ends one subscription by its id, telling its conversation that the app
|
|
305
|
-
* ended it. Returns false if it had already ended (or there's no such
|
|
306
|
-
* subscription of the app's).
|
|
307
|
-
*/
|
|
308
|
-
async endSubscription(subscriptionId) {
|
|
309
|
-
return (await this.request("POST", `/subscriptions/${encodeURIComponent(subscriptionId)}/end`, {})).ended;
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Ends the subscriptions of every conversation of one of the app's users
|
|
313
|
-
* to topic, or to all of the app's topics (they lost access to something),
|
|
314
|
-
* telling each conversation. Returns how many ended.
|
|
315
|
-
*/
|
|
316
|
-
async unsubscribeUser(user, topic) {
|
|
317
|
-
return (await this.request("POST", "/topics/unsubscribe", topic ? { user, topic } : { user })).unsubscribed;
|
|
318
|
-
}
|
|
319
171
|
/**
|
|
320
172
|
* Deletes everything Wakeshift keeps for one of the app's users: their
|
|
321
|
-
* conversations, subscriptions, events and deliveries,
|
|
322
|
-
* their
|
|
323
|
-
* streams close. Returns false if there was nothing to forget.
|
|
173
|
+
* conversations, subscriptions, events and deliveries, publish URLs for
|
|
174
|
+
* them, and their ID. Open streams close. Returns false if there was nothing to forget.
|
|
324
175
|
*/
|
|
325
176
|
async forgetUser(user) {
|
|
326
177
|
return (await this.request("POST", "/users/forget", { user })).forgotten;
|
|
327
178
|
}
|
|
328
179
|
/**
|
|
329
|
-
* Tells Wakeshift what the app may do, replacing what it said before
|
|
330
|
-
*
|
|
331
|
-
*
|
|
180
|
+
* Tells Wakeshift what the app may do, replacing what it said before: m,
|
|
181
|
+
* with the events you defined (defineEvent) after m's own events. An
|
|
182
|
+
* event both defined and listed in m.events is an error, and nothing is
|
|
183
|
+
* sent. You needn't call it: Wakeshift gets the manifest automatically
|
|
184
|
+
* when the app first needs it. Once called, m is what every automatic
|
|
185
|
+
* send carries (even if this one failed), with the events defined by
|
|
186
|
+
* then. Returns the manifest as Wakeshift keeps it.
|
|
332
187
|
*/
|
|
333
188
|
async publishManifest(m) {
|
|
334
|
-
const
|
|
335
|
-
const
|
|
189
|
+
const version = this.manifestVersion; // before the body: a later defineEvent is sent again
|
|
190
|
+
const body = this.manifestBody(m);
|
|
336
191
|
this.manifestBase = m;
|
|
337
|
-
this.
|
|
338
|
-
|
|
192
|
+
const run = this.serial(() => this.request("PUT", "/manifest", body));
|
|
193
|
+
this.track(run, version);
|
|
194
|
+
return run;
|
|
195
|
+
}
|
|
196
|
+
// serial runs a send after every send before it, explicit or automatic,
|
|
197
|
+
// so the last manifest Wakeshift gets is the newest.
|
|
198
|
+
sending = Promise.resolve();
|
|
199
|
+
serial(send) {
|
|
200
|
+
const run = this.sending.then(send, send);
|
|
201
|
+
this.sending = run.catch(() => undefined);
|
|
202
|
+
return run;
|
|
203
|
+
}
|
|
204
|
+
// track makes a send the one event calls wait for: once it's done,
|
|
205
|
+
// Wakeshift has the manifest for the events defined at version, unless it
|
|
206
|
+
// failed (the next call sends again) or an event was defined since.
|
|
207
|
+
track(send, version) {
|
|
208
|
+
const p = send.then(() => {
|
|
209
|
+
if (this.manifestVersion !== version && this.published === p)
|
|
210
|
+
this.published = null;
|
|
211
|
+
});
|
|
212
|
+
this.published = p;
|
|
213
|
+
p.catch(() => {
|
|
214
|
+
if (this.published === p)
|
|
215
|
+
this.published = null;
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
// The manifest as Wakeshift gets it: m's events, then the defined ones,
|
|
219
|
+
// refusing a name in both.
|
|
220
|
+
manifestBody(m) {
|
|
221
|
+
const listed = m.events ?? [];
|
|
222
|
+
for (const name of this.events.keys()) {
|
|
223
|
+
if (listed.some((e) => e.name === name)) {
|
|
224
|
+
throw new Error(`wakeshift: event ${name} is defined with defineEvent and listed in the manifest's events: keep one`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const events = [...listed, ...[...this.events.values()].map((e) => e.manifest)];
|
|
228
|
+
const { events: _listed, ...rest } = m;
|
|
229
|
+
return events.length > 0 ? { ...rest, events } : rest;
|
|
230
|
+
}
|
|
231
|
+
// The automatic send: the manifest the app last published, or else the
|
|
232
|
+
// settings Wakeshift has (asked for first, so the send never wipes what
|
|
233
|
+
// was published separately), with the defined events.
|
|
234
|
+
async sendManifest() {
|
|
235
|
+
if (this.manifestBase) {
|
|
236
|
+
await this.request("PUT", "/manifest", this.manifestBody(this.manifestBase));
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const stored = await this.request("GET", "/manifest");
|
|
240
|
+
const kept = { description: typeof stored.description === "string" ? stored.description : "" };
|
|
241
|
+
if (stored.agent !== null && typeof stored.agent === "object")
|
|
242
|
+
kept.agent = stored.agent;
|
|
243
|
+
if (stored.others_can_wake === true)
|
|
244
|
+
kept.others_can_wake = true;
|
|
245
|
+
if (Array.isArray(stored.preapprove_tools) && stored.preapprove_tools.length > 0)
|
|
246
|
+
kept.preapprove_tools = stored.preapprove_tools;
|
|
247
|
+
await this.request("PUT", "/manifest", this.manifestBody(kept));
|
|
339
248
|
}
|
|
340
249
|
// ── Events (MCP Events, natively or bridged) ──────────────────────────
|
|
341
250
|
events = new Map();
|
|
342
|
-
|
|
251
|
+
// What publishManifest was last given (null: nothing yet); whether
|
|
252
|
+
// Wakeshift has the manifest for the events defined now (the send; null:
|
|
253
|
+
// not yet, or a defineEvent since); and how many defineEvents there were.
|
|
254
|
+
manifestBase = null;
|
|
343
255
|
published = null;
|
|
256
|
+
manifestVersion = 0;
|
|
344
257
|
/**
|
|
345
258
|
* Defines one of the app's event types: what a subscriber picks (args),
|
|
346
259
|
* what each occurrence carries (payload), and the line an agent reads
|
|
@@ -360,7 +273,8 @@ export class Wakeshift {
|
|
|
360
273
|
throw new Error(`wakeshift: event ${name} is defined twice`);
|
|
361
274
|
const ev = new WakeEvent(this, name, opts);
|
|
362
275
|
this.events.set(name, ev);
|
|
363
|
-
this.published = null; // the manifest changed
|
|
276
|
+
this.published = null; // the manifest changed: sent again when next needed
|
|
277
|
+
this.manifestVersion++;
|
|
364
278
|
return ev;
|
|
365
279
|
}
|
|
366
280
|
/**
|
|
@@ -372,21 +286,19 @@ export class Wakeshift {
|
|
|
372
286
|
* unsubscribe_events and list_subscriptions for every client, so a person
|
|
373
287
|
* can always see and stop what they're subscribed to.
|
|
374
288
|
*
|
|
375
|
-
* wake.attach(server, { identify: (extra) => ({ user:
|
|
289
|
+
* const attached = wake.attach(server, { identify: (extra) => ({ user: myUser(extra) }) })
|
|
290
|
+
*
|
|
291
|
+
* It returns the handle your own tools use to subscribe the conversation
|
|
292
|
+
* calling them: wrap a tool's callback with attached.events, and give it
|
|
293
|
+
* Wakeshift's arguments with attached.inputs.
|
|
376
294
|
*/
|
|
377
295
|
attach(server, opts = {}) {
|
|
378
|
-
attachEvents(this, server, [...this.events.values()], opts);
|
|
296
|
+
return attachEvents(this, server, [...this.events.values()], opts);
|
|
379
297
|
}
|
|
380
|
-
/** @internal
|
|
298
|
+
/** @internal Sends the manifest with the defined events, once (again after a defineEvent). */
|
|
381
299
|
ensureEvents() {
|
|
382
|
-
if (!this.published)
|
|
383
|
-
|
|
384
|
-
this.published = p;
|
|
385
|
-
p.catch(() => {
|
|
386
|
-
if (this.published === p)
|
|
387
|
-
this.published = null; // try again next time
|
|
388
|
-
});
|
|
389
|
-
}
|
|
300
|
+
if (!this.published)
|
|
301
|
+
this.track(this.serial(() => this.sendManifest()), this.manifestVersion);
|
|
390
302
|
return this.published;
|
|
391
303
|
}
|
|
392
304
|
/** @internal An event's emit. */
|
|
@@ -398,19 +310,18 @@ export class Wakeshift {
|
|
|
398
310
|
if (e.to)
|
|
399
311
|
body.to = e.to;
|
|
400
312
|
const out = await this.request("POST", "/events/emit", body);
|
|
401
|
-
return { matched: out.matched, duplicate: !!out.duplicate };
|
|
313
|
+
return { matched: out.matched, duplicate: !!out.duplicate, unmatched: recipientsOf(out.unmatched), ended: recipientsOf(out.ended) };
|
|
402
314
|
}
|
|
403
315
|
/** @internal A call to the app API (the events machinery's), after the manifest with the defined events is published. */
|
|
404
316
|
async requestApp(method, path, body) {
|
|
405
317
|
await this.ensureEvents();
|
|
406
318
|
return this.request(method, path, body);
|
|
407
319
|
}
|
|
408
|
-
// ──
|
|
320
|
+
// ── Users' workspaces ──────────────────────────────────────────────────
|
|
409
321
|
/**
|
|
410
322
|
* The workspace Wakeshift keeps for one of the app's users (user: the
|
|
411
|
-
* app's own ID for them, as
|
|
412
|
-
*
|
|
413
|
-
* most apps never need to.
|
|
323
|
+
* app's own ID for them, as attach's identify gives it), created on first
|
|
324
|
+
* use. Wakeshift keeps that user's conversations there.
|
|
414
325
|
*/
|
|
415
326
|
async workspace(user, name) {
|
|
416
327
|
return (await this.request("POST", "/workspaces", { external_id: user, name })).workspace;
|
|
@@ -427,10 +338,6 @@ export class Wakeshift {
|
|
|
427
338
|
async checkAgent(workspace, agent) {
|
|
428
339
|
await this.request("POST", `/workspaces/${encodeURIComponent(workspace)}/agents/${encodeURIComponent(agent)}/token`);
|
|
429
340
|
}
|
|
430
|
-
/** @internal The app key's seed: what GuestAuth keys its signed values from. Never send or log it. */
|
|
431
|
-
secretSeed() {
|
|
432
|
-
return this.key.seed;
|
|
433
|
-
}
|
|
434
341
|
async request(method, path, body) {
|
|
435
342
|
const headers = { Authorization: "Bearer " + this.assertion() };
|
|
436
343
|
if (body !== undefined)
|
|
@@ -457,13 +364,16 @@ export class Wakeshift {
|
|
|
457
364
|
json = undefined;
|
|
458
365
|
}
|
|
459
366
|
if (!resp.ok) {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
367
|
+
// Wakeshift's code, message and reason where it sent them as strings, else
|
|
368
|
+
// the body's start, else the status's text, as every SDK reads it (test/errors.json).
|
|
369
|
+
const e = json?.error !== null && typeof json?.error === "object" ? json.error : {};
|
|
370
|
+
const str = (v) => (typeof v === "string" ? v : "");
|
|
371
|
+
throw new WakeshiftError(resp.status, str(e.code), str(e.message) || text.slice(0, 200) || resp.statusText, {
|
|
372
|
+
reason: str(e.reason) || undefined,
|
|
463
373
|
});
|
|
464
374
|
}
|
|
465
375
|
// A 2xx that isn't Wakeshift's JSON: a proxy's page, or a wrong core.
|
|
466
|
-
if (json === null || typeof json !== "object") {
|
|
376
|
+
if (json === null || typeof json !== "object" || Array.isArray(json)) {
|
|
467
377
|
throw new WakeshiftError(resp.status, "bad_response", `Wakeshift at ${this.core} answered ${resp.status} without a JSON object. Check that WAKESHIFT_CORE, or the core option, is the address of the console your key came from`);
|
|
468
378
|
}
|
|
469
379
|
return json;
|