@wakeshift/sdk 0.3.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 +153 -0
- package/README.md +74 -48
- 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 +91 -51
- package/dist/events.js +178 -72
- package/dist/events.js.map +1 -1
- package/dist/harness.js +1 -1
- package/dist/harness.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/key.d.ts +0 -5
- 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 -253
- package/dist/wakeshift.js +156 -244
- package/dist/wakeshift.js.map +1 -1
- package/package.json +16 -2
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,205 +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
|
-
tool: opts.tool,
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
/** Packs a tool call without an MCP framework. */
|
|
192
|
-
callOf(c) {
|
|
193
|
-
const out = { client: c.clientName ?? "" };
|
|
194
|
-
if (c.user)
|
|
195
|
-
out.user = c.user;
|
|
196
|
-
if (c.meta && Object.keys(c.meta).length > 0)
|
|
197
|
-
out.meta = c.meta;
|
|
198
|
-
if (c.session)
|
|
199
|
-
out.session = c.session;
|
|
200
|
-
if (c.userAgent)
|
|
201
|
-
out.user_agent = c.userAgent;
|
|
202
|
-
if (c.harness?.trim())
|
|
203
|
-
out.harness = c.harness.trim();
|
|
204
|
-
if (c.conversation?.trim())
|
|
205
|
-
out.conversation = c.conversation.trim();
|
|
206
|
-
if (c.tool?.trim())
|
|
207
|
-
out.tool = c.tool.trim();
|
|
208
|
-
return out;
|
|
209
|
-
}
|
|
210
|
-
// recognised reads what the core recognised the caller as, and warns
|
|
211
|
-
// once if it was nothing and the agent didn't say (an older core that
|
|
212
|
-
// doesn't say: no warning).
|
|
213
|
-
recognised(agent) {
|
|
214
|
-
if (agent && !agent.harness && !agent.claimed)
|
|
215
|
-
this.warnOnce("unknown_client", UNKNOWN_CLIENT_WARNING);
|
|
216
|
-
return { harness: agent?.harness ?? null, via: agent?.via ?? null, claimed: agent?.claimed ?? null };
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* Subscribes the calling conversation to topic (again: refreshes it).
|
|
220
|
-
* Check the caller may see the topic first: Wakeshift only refuses another
|
|
221
|
-
* user's u/<user>/… topics.
|
|
222
|
-
*/
|
|
223
|
-
async subscribe(call, topic, opts = {}) {
|
|
224
|
-
const body = { call, topic, once: !!opts.once };
|
|
225
|
-
if (opts.filter && Object.keys(opts.filter).length > 0)
|
|
226
|
-
body.filter = opts.filter;
|
|
227
|
-
if (opts.replay !== undefined)
|
|
228
|
-
body.replay = opts.replay;
|
|
229
|
-
if (opts.ttl !== undefined)
|
|
230
|
-
body.ttl = opts.ttl;
|
|
231
|
-
const out = await this.request("POST", "/topics/subscribe", body);
|
|
232
|
-
const agent = this.recognised(out.agent);
|
|
233
|
-
const addition = out.addition ?? {};
|
|
234
|
-
const s = out.subscription;
|
|
235
|
-
return {
|
|
236
|
-
id: s.id,
|
|
237
|
-
topic: s.topic,
|
|
238
|
-
filter: s.filter,
|
|
239
|
-
once: s.once,
|
|
240
|
-
expiresAt: new Date(s.expires_at),
|
|
241
|
-
session: out.session,
|
|
242
|
-
addition,
|
|
243
|
-
agent,
|
|
244
|
-
result: (text) => applyAddition(addition, text),
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
/** Ends the conversation's subscription to topic, or to all the app's topics. Returns how many ended. */
|
|
248
|
-
async unsubscribe(call, topic) {
|
|
249
|
-
const out = await this.request("POST", "/topics/unsubscribe", topic ? { call, topic } : { call });
|
|
250
|
-
return out.unsubscribed;
|
|
251
|
-
}
|
|
252
|
-
/** Wakes every conversation subscribed to one of topics whose filter the event passes (each once). */
|
|
253
|
-
async publish(topics, event) {
|
|
254
|
-
const out = await this.request("POST", "/topics/publish", { topics: typeof topics === "string" ? [topics] : topics, event });
|
|
255
|
-
return { matched: out.matched, duplicate: !!out.duplicate };
|
|
256
|
-
}
|
|
257
|
-
/** Wakes exactly one subscription's conversation, whatever its filter. */
|
|
258
|
-
async send(subscriptionId, event) {
|
|
259
|
-
const out = await this.request("POST", `/subscriptions/${encodeURIComponent(subscriptionId)}/send`, { event });
|
|
260
|
-
return out.status;
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* How many conversations are subscribed to topic, and how many are
|
|
264
|
-
* connected now, and how. Only reads: nothing is delivered, minted or ended.
|
|
265
|
-
*/
|
|
266
|
-
async topicStatus(topic) {
|
|
267
|
-
const out = await this.request("GET", `/topics/status?topic=${encodeURIComponent(topic)}`);
|
|
268
|
-
return { topic: out.topic, subscriptions: out.subscriptions, connected: out.connected, via: { stream: 0, poll: 0, hooks: 0, webhook: 0, ...out.via } };
|
|
269
|
-
}
|
|
270
|
-
/** Whether a subscription is active and its conversation connected now, and how. Only reads. */
|
|
271
|
-
async subscriptionStatus(subscriptionId) {
|
|
272
|
-
const out = await this.request("GET", `/subscriptions/${encodeURIComponent(subscriptionId)}`);
|
|
273
|
-
const s = { id: out.id, topic: out.topic, status: out.status, connected: out.connected };
|
|
274
|
-
if (out.via)
|
|
275
|
-
s.via = out.via;
|
|
276
|
-
if (out.expires_at)
|
|
277
|
-
s.expiresAt = new Date(out.expires_at);
|
|
278
|
-
return s;
|
|
279
|
-
}
|
|
280
|
-
/** A capability URL that publishes to topic: a POST's body becomes an event. */
|
|
281
|
-
async publishUrl(topic) {
|
|
282
|
-
return this.request("POST", "/topics/publish-urls", { topic });
|
|
283
|
-
}
|
|
284
|
-
/** 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). */
|
|
285
168
|
async revokePublishUrl(id) {
|
|
286
169
|
await this.request("DELETE", `/events/publish-urls/${encodeURIComponent(id)}`);
|
|
287
170
|
}
|
|
288
|
-
/** What Wakeshift would add to any other tool result for this conversation (creates nothing). */
|
|
289
|
-
async addition(call) {
|
|
290
|
-
const out = await this.request("POST", "/topics/annotate", { call });
|
|
291
|
-
return { ...out, agent: this.recognised(out.agent) };
|
|
292
|
-
}
|
|
293
|
-
/**
|
|
294
|
-
* Adds what Wakeshift has for the calling conversation to a tool result
|
|
295
|
-
* of yours (a copy): what's waiting for a harness that can't be woken, a
|
|
296
|
-
* fresh connector beacon while a Codex thread's connector isn't running.
|
|
297
|
-
* Only results you pass here (and a subscription's result()) change.
|
|
298
|
-
*/
|
|
299
|
-
async annotate(call, result) {
|
|
300
|
-
return applyAddition(await this.addition(call), result);
|
|
301
|
-
}
|
|
302
|
-
/** Ends every subscription to topic; each conversation is told. Returns how many ended. */
|
|
303
|
-
async closeTopic(topic) {
|
|
304
|
-
return (await this.request("POST", "/topics/close", { topic })).closed;
|
|
305
|
-
}
|
|
306
|
-
/**
|
|
307
|
-
* Ends one subscription by its id, telling its conversation that the app
|
|
308
|
-
* ended it. Returns false if it had already ended (or there's no such
|
|
309
|
-
* subscription of the app's).
|
|
310
|
-
*/
|
|
311
|
-
async endSubscription(subscriptionId) {
|
|
312
|
-
return (await this.request("POST", `/subscriptions/${encodeURIComponent(subscriptionId)}/end`, {})).ended;
|
|
313
|
-
}
|
|
314
|
-
/**
|
|
315
|
-
* Ends the subscriptions of every conversation of one of the app's users
|
|
316
|
-
* to topic, or to all of the app's topics (they lost access to something),
|
|
317
|
-
* telling each conversation. Returns how many ended.
|
|
318
|
-
*/
|
|
319
|
-
async unsubscribeUser(user, topic) {
|
|
320
|
-
return (await this.request("POST", "/topics/unsubscribe", topic ? { user, topic } : { user })).unsubscribed;
|
|
321
|
-
}
|
|
322
171
|
/**
|
|
323
172
|
* Deletes everything Wakeshift keeps for one of the app's users: their
|
|
324
|
-
* conversations, subscriptions, events and deliveries,
|
|
325
|
-
* their
|
|
326
|
-
* 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.
|
|
327
175
|
*/
|
|
328
176
|
async forgetUser(user) {
|
|
329
177
|
return (await this.request("POST", "/users/forget", { user })).forgotten;
|
|
330
178
|
}
|
|
331
179
|
/**
|
|
332
|
-
* Tells Wakeshift what the app may do, replacing what it said before
|
|
333
|
-
*
|
|
334
|
-
*
|
|
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.
|
|
335
187
|
*/
|
|
336
188
|
async publishManifest(m) {
|
|
337
|
-
const
|
|
338
|
-
const
|
|
189
|
+
const version = this.manifestVersion; // before the body: a later defineEvent is sent again
|
|
190
|
+
const body = this.manifestBody(m);
|
|
339
191
|
this.manifestBase = m;
|
|
340
|
-
this.
|
|
341
|
-
|
|
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));
|
|
342
248
|
}
|
|
343
249
|
// ── Events (MCP Events, natively or bridged) ──────────────────────────
|
|
344
250
|
events = new Map();
|
|
345
|
-
|
|
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;
|
|
346
255
|
published = null;
|
|
256
|
+
manifestVersion = 0;
|
|
347
257
|
/**
|
|
348
258
|
* Defines one of the app's event types: what a subscriber picks (args),
|
|
349
259
|
* what each occurrence carries (payload), and the line an agent reads
|
|
@@ -363,7 +273,8 @@ export class Wakeshift {
|
|
|
363
273
|
throw new Error(`wakeshift: event ${name} is defined twice`);
|
|
364
274
|
const ev = new WakeEvent(this, name, opts);
|
|
365
275
|
this.events.set(name, ev);
|
|
366
|
-
this.published = null; // the manifest changed
|
|
276
|
+
this.published = null; // the manifest changed: sent again when next needed
|
|
277
|
+
this.manifestVersion++;
|
|
367
278
|
return ev;
|
|
368
279
|
}
|
|
369
280
|
/**
|
|
@@ -375,21 +286,19 @@ export class Wakeshift {
|
|
|
375
286
|
* unsubscribe_events and list_subscriptions for every client, so a person
|
|
376
287
|
* can always see and stop what they're subscribed to.
|
|
377
288
|
*
|
|
378
|
-
* wake.attach(server, { identify: (extra) => ({ user: myUser(extra) }) })
|
|
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.
|
|
379
294
|
*/
|
|
380
295
|
attach(server, opts = {}) {
|
|
381
|
-
attachEvents(this, server, [...this.events.values()], opts);
|
|
296
|
+
return attachEvents(this, server, [...this.events.values()], opts);
|
|
382
297
|
}
|
|
383
|
-
/** @internal
|
|
298
|
+
/** @internal Sends the manifest with the defined events, once (again after a defineEvent). */
|
|
384
299
|
ensureEvents() {
|
|
385
|
-
if (!this.published)
|
|
386
|
-
|
|
387
|
-
this.published = p;
|
|
388
|
-
p.catch(() => {
|
|
389
|
-
if (this.published === p)
|
|
390
|
-
this.published = null; // try again next time
|
|
391
|
-
});
|
|
392
|
-
}
|
|
300
|
+
if (!this.published)
|
|
301
|
+
this.track(this.serial(() => this.sendManifest()), this.manifestVersion);
|
|
393
302
|
return this.published;
|
|
394
303
|
}
|
|
395
304
|
/** @internal An event's emit. */
|
|
@@ -401,7 +310,7 @@ export class Wakeshift {
|
|
|
401
310
|
if (e.to)
|
|
402
311
|
body.to = e.to;
|
|
403
312
|
const out = await this.request("POST", "/events/emit", body);
|
|
404
|
-
return { matched: out.matched, duplicate: !!out.duplicate };
|
|
313
|
+
return { matched: out.matched, duplicate: !!out.duplicate, unmatched: recipientsOf(out.unmatched), ended: recipientsOf(out.ended) };
|
|
405
314
|
}
|
|
406
315
|
/** @internal A call to the app API (the events machinery's), after the manifest with the defined events is published. */
|
|
407
316
|
async requestApp(method, path, body) {
|
|
@@ -411,8 +320,8 @@ export class Wakeshift {
|
|
|
411
320
|
// ── Users' workspaces ──────────────────────────────────────────────────
|
|
412
321
|
/**
|
|
413
322
|
* The workspace Wakeshift keeps for one of the app's users (user: the
|
|
414
|
-
* app's own ID for them, as
|
|
415
|
-
*
|
|
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.
|
|
416
325
|
*/
|
|
417
326
|
async workspace(user, name) {
|
|
418
327
|
return (await this.request("POST", "/workspaces", { external_id: user, name })).workspace;
|
|
@@ -455,13 +364,16 @@ export class Wakeshift {
|
|
|
455
364
|
json = undefined;
|
|
456
365
|
}
|
|
457
366
|
if (!resp.ok) {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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,
|
|
461
373
|
});
|
|
462
374
|
}
|
|
463
375
|
// A 2xx that isn't Wakeshift's JSON: a proxy's page, or a wrong core.
|
|
464
|
-
if (json === null || typeof json !== "object") {
|
|
376
|
+
if (json === null || typeof json !== "object" || Array.isArray(json)) {
|
|
465
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`);
|
|
466
378
|
}
|
|
467
379
|
return json;
|