swarmbox 0.12.0 → 0.14.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.
Files changed (56) hide show
  1. package/README.md +11 -2
  2. package/dist/fleet/browser.d.ts +10 -4
  3. package/dist/fleet/browser.d.ts.map +1 -1
  4. package/dist/fleet/browser.js +15 -5
  5. package/dist/fleet/browser.js.map +1 -1
  6. package/dist/fleet/sight.d.ts +26 -2
  7. package/dist/fleet/sight.d.ts.map +1 -1
  8. package/dist/fleet/sight.js +146 -17
  9. package/dist/fleet/sight.js.map +1 -1
  10. package/dist/harness.d.ts +36 -1
  11. package/dist/harness.d.ts.map +1 -1
  12. package/dist/harness.js +160 -23
  13. package/dist/harness.js.map +1 -1
  14. package/dist/hooks.d.ts.map +1 -1
  15. package/dist/hooks.js +5 -1
  16. package/dist/hooks.js.map +1 -1
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/mcp/authenticator.d.ts +17 -0
  22. package/dist/mcp/authenticator.d.ts.map +1 -0
  23. package/dist/mcp/authenticator.js +63 -0
  24. package/dist/mcp/authenticator.js.map +1 -0
  25. package/dist/mcp/inbox.d.ts +16 -0
  26. package/dist/mcp/inbox.d.ts.map +1 -0
  27. package/dist/mcp/inbox.js +55 -0
  28. package/dist/mcp/inbox.js.map +1 -0
  29. package/dist/mcp/server.d.ts.map +1 -1
  30. package/dist/mcp/server.js +103 -15
  31. package/dist/mcp/server.js.map +1 -1
  32. package/dist/product.d.ts +27 -3
  33. package/dist/product.d.ts.map +1 -1
  34. package/dist/redact.d.ts +13 -0
  35. package/dist/redact.d.ts.map +1 -0
  36. package/dist/redact.js +226 -0
  37. package/dist/redact.js.map +1 -0
  38. package/dist/requests.d.ts +60 -0
  39. package/dist/requests.d.ts.map +1 -0
  40. package/dist/requests.js +137 -0
  41. package/dist/requests.js.map +1 -0
  42. package/dist/standins/builtin/email/index.d.ts.map +1 -1
  43. package/dist/standins/builtin/email/index.js +238 -8
  44. package/dist/standins/builtin/email/index.js.map +1 -1
  45. package/dist/standins/builtin/email/manifest.json +6 -2
  46. package/dist/standins/manifest.d.ts +1 -1
  47. package/dist/standins/runner.d.ts.map +1 -1
  48. package/dist/standins/runner.js +25 -9
  49. package/dist/standins/runner.js.map +1 -1
  50. package/dist/standins/sdk.js +8 -0
  51. package/dist/standins/sdk.js.map +1 -1
  52. package/dist/time.d.ts +6 -0
  53. package/dist/time.d.ts.map +1 -1
  54. package/dist/time.js +18 -0
  55. package/dist/time.js.map +1 -1
  56. package/package.json +1 -1
package/dist/hooks.js CHANGED
@@ -4,7 +4,9 @@
4
4
  //
5
5
  // They answer on the product's bare host only, never a tenant's (where testers' browsers are signed in), so
6
6
  // requests go to a loopback base such as http://127.0.0.1:<port>/_sandbox. A product build without them
7
- // answers 404 on /health; every call then says so instead of failing obscurely.
7
+ // answers 404 on /health; every call then says so instead of failing obscurely. A product may serve only some
8
+ // of them (the clock, say, and no feed): a hook it doesn't serve answers a plain 404, not the protocol's
9
+ // {ok: false}, and is reported as missing, so the harness goes on without it.
8
10
  //
9
11
  // GET /health 200 when the hooks are there
10
12
  // GET /clock, POST /clock sandbox time; move it: {jumpMs} | {to} | {pause} | {play}
@@ -44,6 +46,8 @@ export function hooksFor(base, token, fetchImpl = fetch) {
44
46
  catch {
45
47
  // Not JSON: reported below with the status.
46
48
  }
49
+ if (res.status === 404 && json.ok !== false)
50
+ throw new HooksMissing(`this product serves no ${prefix}${path.split('?')[0]} hook`);
47
51
  if (!res.ok || json.ok === false)
48
52
  throw new HooksRefused(`${method} ${prefix}${path}: ${res.status}${json.error ? ` ${json.error.code ?? ''}: ${json.error.message ?? ''}` : ` ${text.slice(0, 200)}`}`);
49
53
  return (json.result ?? json);
package/dist/hooks.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export type { FindingSink, Persona, Product, ProductWorld, SignIn, SinkFinding, SinkRun, StartCookie, ToolContext, WorldSpec, } from './product.ts';
2
+ export { redactBody, redactUrl, type RequestLog, type RequestQuery, type RequestRecord } from './requests.ts';
2
3
  export { serve, DEFAULT_PORTS, type ServeOptions, type Served } from './serve.ts';
3
4
  export { createHarness, feedLine, globMatches, HarnessError, linkPath, type Action, type Controller, type Harness, type HarnessOptions, type NetworkMode, type OpenScreenOptions, type ScreenEntry, type Signal, type WorldEntry, } from './harness.ts';
4
5
  export { serveMcp, buildServer, guard, type McpHttp } from './mcp/server.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // Swarmbox: a sandbox where AI agents use your product the way people do, in real browsers against
2
2
  // throwaway worlds, and see what a person sees and what the server did. A product plugs in with one object
3
3
  // (Product, in product.ts); everything else here is the same for every product.
4
+ export { redactBody, redactUrl } from "./requests.js";
4
5
  export { serve, DEFAULT_PORTS } from "./serve.js";
5
6
  export { createHarness, feedLine, globMatches, HarnessError, linkPath, } from "./harness.js";
6
7
  export { serveMcp, buildServer, guard } from "./mcp/server.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,2GAA2G;AAC3G,gFAAgF;AAchF,OAAO,EAAE,KAAK,EAAE,aAAa,EAAkC,MAAM,YAAY,CAAC;AAClF,OAAO,EACL,aAAa,EACb,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,QAAQ,GAUT,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAgB,MAAM,iBAAiB,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAoB,MAAM,0BAA0B,CAAC;AAC9E,OAAO,EACL,UAAU,GAMX,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,UAAU,EAAE,YAAY,EAAkD,MAAM,kBAAkB,CAAC;AAC5G,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC1F,OAAO,EACL,OAAO,EACP,YAAY,GAMb,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,QAAQ,EACR,YAAY,EACZ,YAAY,GAOb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAA2C,MAAM,iBAAiB,CAAC;AAC1G,OAAO,EACL,OAAO,EACP,WAAW,EACX,YAAY,GAMb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,WAAW,EACX,OAAO,EACP,WAAW,EACX,IAAI,EACJ,MAAM,EACN,WAAW,EACX,SAAS,EACT,aAAa,EACb,SAAS,GAIV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,UAAU,EACV,OAAO,EACP,QAAQ,EACR,SAAS,EACT,WAAW,GAEZ,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAA8C,MAAM,wBAAwB,CAAC;AACpG,OAAO,EAAE,YAAY,EAAoD,MAAM,sBAAsB,CAAC;AACtG,OAAO,EACL,aAAa,EACb,KAAK,EACL,IAAI,EACJ,cAAc,GAKf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAoB,MAAM,qBAAqB,CAAC;AACvF,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAuB,MAAM,6BAA6B,CAAC;AACpG,OAAO,EAAE,SAAS,EAA4C,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,2GAA2G;AAC3G,gFAAgF;AAchF,OAAO,EAAE,UAAU,EAAE,SAAS,EAA0D,MAAM,eAAe,CAAC;AAC9G,OAAO,EAAE,KAAK,EAAE,aAAa,EAAkC,MAAM,YAAY,CAAC;AAClF,OAAO,EACL,aAAa,EACb,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,QAAQ,GAUT,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAgB,MAAM,iBAAiB,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAoB,MAAM,0BAA0B,CAAC;AAC9E,OAAO,EACL,UAAU,GAMX,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,UAAU,EAAE,YAAY,EAAkD,MAAM,kBAAkB,CAAC;AAC5G,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC1F,OAAO,EACL,OAAO,EACP,YAAY,GAMb,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,QAAQ,EACR,YAAY,EACZ,YAAY,GAOb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAA2C,MAAM,iBAAiB,CAAC;AAC1G,OAAO,EACL,OAAO,EACP,WAAW,EACX,YAAY,GAMb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,WAAW,EACX,OAAO,EACP,WAAW,EACX,IAAI,EACJ,MAAM,EACN,WAAW,EACX,SAAS,EACT,aAAa,EACb,SAAS,GAIV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,UAAU,EACV,OAAO,EACP,QAAQ,EACR,SAAS,EACT,WAAW,GAEZ,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAA8C,MAAM,wBAAwB,CAAC;AACpG,OAAO,EAAE,YAAY,EAAoD,MAAM,sBAAsB,CAAC;AACtG,OAAO,EACL,aAAa,EACb,KAAK,EACL,IAAI,EACJ,cAAc,GAKf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAoB,MAAM,qBAAqB,CAAC;AACvF,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAuB,MAAM,6BAA6B,CAAC;AACpG,OAAO,EAAE,SAAS,EAA4C,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,17 @@
1
+ export type Algorithm = 'SHA1' | 'SHA256' | 'SHA512';
2
+ export interface TotpKey {
3
+ secret: Buffer;
4
+ digits: number;
5
+ period: number;
6
+ algorithm: Algorithm;
7
+ }
8
+ /** RFC 4648 base32, as a setup screen shows it: any case, spaces and dashes between groups, padding optional. */
9
+ export declare function base32Decode(text: string): Buffer;
10
+ /** A key as typed into the app: the base32 secret alone, or the otpauth:// link a QR code holds. */
11
+ export declare function parseKey(input: string): TotpKey;
12
+ /** The code at an instant (ms), and how many seconds it has left. */
13
+ export declare function totp(key: TotpKey, atMs: number): {
14
+ code: string;
15
+ secondsLeft: number;
16
+ };
17
+ //# sourceMappingURL=authenticator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authenticator.d.ts","sourceRoot":"","sources":["../../src/mcp/authenticator.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAErD,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,CAAC;CACtB;AAID,iHAAiH;AACjH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAkBjD;AAED,oGAAoG;AACpG,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAiB/C;AAED,qEAAqE;AACrE,wBAAgB,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAWtF"}
@@ -0,0 +1,63 @@
1
+ // The authenticator app on a person's phone: a TOTP code (RFC 6238) from the key a product's two-step setup
2
+ // shows, at the world's own time, since that's the clock the product checks it against. Without it an agent
3
+ // can't turn two-step sign-in on, or play anything a product puts behind it.
4
+ import { createHmac } from 'node:crypto';
5
+ const BASE32 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
6
+ /** RFC 4648 base32, as a setup screen shows it: any case, spaces and dashes between groups, padding optional. */
7
+ export function base32Decode(text) {
8
+ const clean = text
9
+ .replace(/[\s-]+/g, '')
10
+ .replace(/=+$/, '')
11
+ .toUpperCase();
12
+ if (!clean || /[^A-Z2-7]/.test(clean))
13
+ throw new Error('the key isn’t base32 (letters A–Z and digits 2–7)');
14
+ let bits = 0;
15
+ let value = 0;
16
+ const out = [];
17
+ for (const c of clean) {
18
+ value = (value << 5) | BASE32.indexOf(c);
19
+ bits += 5;
20
+ if (bits >= 8) {
21
+ out.push((value >>> (bits - 8)) & 0xff);
22
+ bits -= 8;
23
+ }
24
+ }
25
+ return Buffer.from(out);
26
+ }
27
+ /** A key as typed into the app: the base32 secret alone, or the otpauth:// link a QR code holds. */
28
+ export function parseKey(input) {
29
+ const given = input.trim();
30
+ if (!given.toLowerCase().startsWith('otpauth://'))
31
+ return { secret: base32Decode(given), digits: 6, period: 30, algorithm: 'SHA1' };
32
+ const url = new URL(given);
33
+ if (url.host.toLowerCase() !== 'totp')
34
+ throw new Error(`an otpauth link for ${url.host}, not totp`);
35
+ const q = url.searchParams;
36
+ const secret = q.get('secret');
37
+ if (!secret)
38
+ throw new Error('the otpauth link has no secret');
39
+ const digits = Number(q.get('digits') ?? 6);
40
+ const period = Number(q.get('period') ?? 30);
41
+ const algorithm = (q.get('algorithm') ?? 'SHA1').toUpperCase();
42
+ if (![6, 7, 8].includes(digits))
43
+ throw new Error(`digits ${digits}: an app shows 6, 7 or 8`);
44
+ if (!Number.isInteger(period) || period < 1)
45
+ throw new Error(`period ${q.get('period')}: whole seconds`);
46
+ if (algorithm !== 'SHA1' && algorithm !== 'SHA256' && algorithm !== 'SHA512')
47
+ throw new Error(`algorithm ${algorithm}: SHA1, SHA256 or SHA512`);
48
+ return { secret: base32Decode(secret), digits, period, algorithm };
49
+ }
50
+ /** The code at an instant (ms), and how many seconds it has left. */
51
+ export function totp(key, atMs) {
52
+ const step = Math.floor(atMs / 1000 / key.period);
53
+ const counter = Buffer.alloc(8);
54
+ counter.writeBigUInt64BE(BigInt(step));
55
+ const mac = createHmac(key.algorithm.toLowerCase(), key.secret).update(counter).digest();
56
+ const at = mac[mac.length - 1] & 0x0f;
57
+ const n = mac.readUInt32BE(at) & 0x7fffffff;
58
+ return {
59
+ code: String(n % 10 ** key.digits).padStart(key.digits, '0'),
60
+ secondsLeft: key.period - (Math.floor(atMs / 1000) % key.period),
61
+ };
62
+ }
63
+ //# sourceMappingURL=authenticator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authenticator.js","sourceRoot":"","sources":["../../src/mcp/authenticator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { FeedPage, Hooks } from '../hooks.ts';
2
+ /** One cursor for the two mailboxes, as `next`: each one's own, kept apart. */
3
+ export declare function joinCursor(own: string, platform: string): string;
4
+ /** The two cursors a joined one holds. A product's own cursor (from before the merge) is the tenant's. */
5
+ export declare function splitCursor(after: string | undefined): {
6
+ own?: string;
7
+ platform?: string;
8
+ };
9
+ /**
10
+ * The tenant's messages to `to`, and the platform mailbox's when the product has one, in time order. A platform
11
+ * mailbox the product refuses to read leaves the tenant's messages, with a note saying why.
12
+ */
13
+ export declare function readInbox(hooks: Pick<Hooks, 'messages'>, tenant: string, to: string, after: string | undefined, platformMailbox: string | undefined): Promise<FeedPage & {
14
+ note?: string;
15
+ }>;
16
+ //# sourceMappingURL=inbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbox.d.ts","sourceRoot":"","sources":["../../src/mcp/inbox.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAY,QAAQ,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAI7D,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,0GAA0G;AAC1G,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAY1F;AAWD;;;GAGG;AACH,wBAAsB,SAAS,CAC7B,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,EAC9B,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,eAAe,EAAE,MAAM,GAAG,SAAS,GAClC,OAAO,CAAC,QAAQ,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAkBvC"}
@@ -0,0 +1,55 @@
1
+ // One person's inbox, as phone_inbox gives it: the tenant's own messages to them, and the ones the product
2
+ // sends as itself (sign-in codes, invites, sign-up confirmations), which a product files under its platform
3
+ // mailbox, not under any tenant. Read apart, a tester looking in the company's mailbox finds no code and
4
+ // says it never came, though it was sent.
5
+ const BOTH = 'both:';
6
+ /** One cursor for the two mailboxes, as `next`: each one's own, kept apart. */
7
+ export function joinCursor(own, platform) {
8
+ return BOTH + Buffer.from(JSON.stringify([own, platform])).toString('base64url');
9
+ }
10
+ /** The two cursors a joined one holds. A product's own cursor (from before the merge) is the tenant's. */
11
+ export function splitCursor(after) {
12
+ if (!after)
13
+ return {};
14
+ if (!after.startsWith(BOTH))
15
+ return { own: after };
16
+ try {
17
+ const [own, platform] = JSON.parse(Buffer.from(after.slice(BOTH.length), 'base64url').toString());
18
+ return { ...(own ? { own } : {}), ...(platform ? { platform } : {}) };
19
+ }
20
+ catch {
21
+ return { own: after };
22
+ }
23
+ }
24
+ /** A platform message, marked so the agent reads who sent it. */
25
+ const asPlatform = ({ cursor, kind, at, ...rest }) => ({
26
+ cursor,
27
+ kind,
28
+ at,
29
+ sentAs: 'the platform',
30
+ ...rest,
31
+ });
32
+ /**
33
+ * The tenant's messages to `to`, and the platform mailbox's when the product has one, in time order. A platform
34
+ * mailbox the product refuses to read leaves the tenant's messages, with a note saying why.
35
+ */
36
+ export async function readInbox(hooks, tenant, to, after, platformMailbox) {
37
+ const at = splitCursor(after);
38
+ const own = await hooks.messages(tenant, to, at.own ? { after: at.own } : {});
39
+ if (!platformMailbox || platformMailbox === tenant)
40
+ return own;
41
+ let platform;
42
+ try {
43
+ platform = await hooks.messages(platformMailbox, to, at.platform ? { after: at.platform } : {});
44
+ }
45
+ catch (e) {
46
+ return { ...own, note: `the platform mailbox (${platformMailbox}) wasn’t read: ${e.message}` };
47
+ }
48
+ const items = [...own.items, ...platform.items.map(asPlatform)].sort((a, b) => Date.parse(a.at) - Date.parse(b.at));
49
+ return {
50
+ items,
51
+ next: joinCursor(own.next, platform.next),
52
+ lagMs: Math.max(own.lagMs, platform.lagMs),
53
+ };
54
+ }
55
+ //# sourceMappingURL=inbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbox.js","sourceRoot":"","sources":["../../src/mcp/inbox.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAOzE,OAAO,EAA6B,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AAExE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAKvD,2FAA2F;AAC3F,wBAAsB,KAAK,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAatF;AAmBD,wBAAgB,WAAW,CAAC,CAAC,SAAS,YAAY,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,GAAE,IAAoB,GAAG,SAAS,CAkyBxG;AAED,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,0FAA0F;AAC1F,wBAAsB,QAAQ,CAAC,CAAC,SAAS,YAAY,EACnD,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,EACb,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,IAAoB,GACzB,OAAO,CAAC,OAAO,CAAC,CAuDlB"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAWzE,OAAO,EAA6C,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AAExF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAKvD,2FAA2F;AAC3F,wBAAsB,KAAK,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAatF;AAsBD,wBAAgB,WAAW,CAAC,CAAC,SAAS,YAAY,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,GAAE,IAAoB,GAAG,SAAS,CAo4BxG;AAED,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,0FAA0F;AAC1F,wBAAsB,QAAQ,CAAC,CAAC,SAAS,YAAY,EACnD,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,EACb,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,IAAoB,GACzB,OAAO,CAAC,OAAO,CAAC,CAuDlB"}
@@ -13,9 +13,13 @@ import { z } from 'zod';
13
13
  import { failing, formatReport } from "../checks.js";
14
14
  import { DEVICES } from "../fleet/browser.js";
15
15
  import { registerDeviceTools } from "./device.js";
16
+ import { parseKey, totp } from "./authenticator.js";
17
+ import { readInbox } from "./inbox.js";
16
18
  import { registerStandInTools } from "./standins.js";
17
19
  import { describeProvenance } from "../provenance.js";
18
- import { HarnessError } from "../harness.js";
20
+ import { REQUESTS_KEPT, requestLine } from "../requests.js";
21
+ import { wallClock } from "../time.js";
22
+ import { HarnessError, refreshTenants } from "../harness.js";
19
23
  import { StandInError } from "../standins/runner.js";
20
24
  import { createRuns } from "../runs/run.js";
21
25
  import { listScenarios, loadScenario, saveScenario, Scenario } from "../scenarios/spec.js";
@@ -44,6 +48,9 @@ function describeWorld(h, id) {
44
48
  ...(w.world.notes?.() ?? []).map((n) => ` ${n}`),
45
49
  ...w.world.tenants.map((t) => ` ${t}: ${w.world.origin(t)}`),
46
50
  ` screens: ${open.map((s) => `${s.id} ${s.persona.label} [${s.controller}]`).join('; ') || 'none'}`,
51
+ w.contained
52
+ ? ' browsers: contained (only the world, its stand-ins and browserAllow)'
53
+ : ' browsers: not contained (Product.contain is off): its pages can reach anything this machine can',
47
54
  ...(w.standins.size
48
55
  ? [
49
56
  ` stand-ins: ${[...w.standins.values()].map((s) => `${s.manifest.name} (${s.manifest.provider}) at ${s.origin}`).join('; ')}`,
@@ -58,8 +65,8 @@ export function buildServer(h, runs = createRuns(h)) {
58
65
  server.registerTool('world_create', {
59
66
  title: 'Create a world',
60
67
  description: `Start a world: a running copy of ${product.name} with its own data and the ${noun}s given (default ` +
61
- 'one), started the way it is deployed. Outside services are fakes; nothing leaves this machine. Seed ' +
62
- 'adds demo data and takes longer.',
68
+ 'one), started the way it is deployed, with stand-ins for the outside services the product names. ' +
69
+ 'world_list says whether its browsers are contained. Seed adds demo data and takes longer.',
63
70
  inputSchema: {
64
71
  tenants: z
65
72
  .array(z.string().regex(/^[a-z][a-z0-9-]{1,38}[a-z0-9]$/))
@@ -124,10 +131,14 @@ export function buildServer(h, runs = createRuns(h)) {
124
131
  title: 'List worlds',
125
132
  description: `The worlds running now, their ${noun}s' addresses and open screens.`,
126
133
  annotations: { readOnlyHint: true },
127
- }, async () => guard(async () => text(h
128
- .worlds()
129
- .map((w) => describeWorld(h, w.id))
130
- .join('\n\n') || 'no worlds running')));
134
+ }, async () => guard(async () => {
135
+ // A tenant made during a run (the product's own sign-up) is listed with the rest.
136
+ await Promise.all(h.worlds().map((w) => refreshTenants(w)));
137
+ return text(h
138
+ .worlds()
139
+ .map((w) => describeWorld(h, w.id))
140
+ .join('\n\n') || 'no worlds running');
141
+ }));
131
142
  const hooksOf = (world) => h.hooks(world);
132
143
  const feedText = (p) => [
133
144
  `${p.items.length} items; next ${p.next}; lag ${p.lagMs} ms`,
@@ -139,6 +150,10 @@ export function buildServer(h, runs = createRuns(h)) {
139
150
  server.registerTool('world_clock', {
140
151
  title: 'A world’s clock',
141
152
  description: 'Read the world’s sandbox time, or move it: jumpMs forward, to an instant, pause or play. Forward only. ' +
153
+ 'It also gives the wall clock in each zone the world’s people are in, with its offset: check a screen’s times ' +
154
+ 'against that. ' +
155
+ 'Moving the clock doesn’t make the product’s work go faster: a long job (an import, a sync) still takes real ' +
156
+ 'time, so wait for it on the screen rather than jumping past it. ' +
142
157
  'The product follows it, and so does every open screen’s browser, since "today" is decided there. Needs ' +
143
158
  'the product’s hooks.',
144
159
  inputSchema: {
@@ -163,8 +178,12 @@ export function buildServer(h, runs = createRuns(h)) {
163
178
  : undefined;
164
179
  const st = await h.moveClock(world, move);
165
180
  const screensFollowing = h.screens().filter((x) => x.worldId === world && x.clockInstalled).length;
181
+ // Each zone its people are in, as their screens should show it.
182
+ const zones = [...new Set((await h.people(world)).map((p) => p.timeZone))];
183
+ const at = new Date(st.process.at);
166
184
  return text(`${world}: sandbox time ${st.process.at}${st.clock.frozenAt ? ' (paused)' : ''}; offset ${Math.round(st.clock.offsetMs / 1000)} s` +
167
- (move ? `; ${screensFollowing} screens follow it` : ''));
185
+ (move ? `; ${screensFollowing} screens follow it` : '') +
186
+ zones.map((z) => `\n in ${z}: ${wallClock(at, z)}`).join(''));
168
187
  }));
169
188
  server.registerTool('feed_read', {
170
189
  title: `What happened in a ${noun}`,
@@ -189,17 +208,48 @@ export function buildServer(h, runs = createRuns(h)) {
189
208
  server.registerTool('phone_inbox', {
190
209
  title: 'A phone’s or inbox’s messages',
191
210
  description: `Every text and email between the ${noun} and one number or address, as the fakes delivered them: ` +
192
- 'the phone a customer holds. Needs the product’s hooks.',
211
+ 'the phone a customer holds. ' +
212
+ (product.platformMailbox
213
+ ? `With them, what ${product.name} sent that person as itself, not as a ${noun} (sign-in codes, invites, ` +
214
+ 'sign-up confirmations), marked sentAs "the platform". '
215
+ : '') +
216
+ 'Needs the product’s hooks.',
193
217
  inputSchema: {
194
218
  world: z.string(),
195
- tenant: z.string(),
219
+ tenant: z.string().optional().describe(`Which ${noun}; default the world’s first`),
196
220
  to: z.string().min(3).describe('Whose: a phone number (+1555…) or an email address'),
197
- after: z.string().optional(),
221
+ after: z.string().optional().describe('The `next` of an earlier read: only what came since'),
198
222
  },
199
223
  annotations: { readOnlyHint: true },
200
224
  }, async ({ world, tenant, to, after }) => guard(async () => {
201
225
  const hooks = await hooksOf(world);
202
- return text(feedText((await hooks.messages(tenant, to, after ? { after } : {}))));
226
+ const t = tenant ?? h.world(world).world.tenants[0];
227
+ const page = await readInbox(hooks, t, to, after, product.platformMailbox);
228
+ return text([feedText(page), ...(page.note ? [page.note] : [])].join('\n'));
229
+ }));
230
+ server.registerTool('phone_authenticator', {
231
+ title: 'The authenticator app on a person’s phone',
232
+ description: 'The code an authenticator app shows for a key, at the world’s time (the clock the product checks it ' +
233
+ 'against): give the key a two-step setup screen shows (the letters and digits under the QR code, spaces ' +
234
+ 'and all) or its otpauth:// link. Read the key off the screen; a key is the person’s, so don’t put it in a ' +
235
+ 'finding. Needs the product’s hooks.',
236
+ inputSchema: {
237
+ world: z.string(),
238
+ key: z.string().min(16).describe('The setup key, or an otpauth://totp/ link'),
239
+ },
240
+ annotations: { readOnlyHint: true },
241
+ }, async ({ world, key }) => guard(async () => {
242
+ let parsed;
243
+ try {
244
+ parsed = parseKey(key);
245
+ }
246
+ catch (e) {
247
+ throw new HarnessError(e.message);
248
+ }
249
+ const at = Date.parse((await h.moveClock(world)).process.at);
250
+ const now = totp(parsed, at);
251
+ const next = totp(parsed, at + now.secondsLeft * 1000);
252
+ return text(`${now.code} (for ${now.secondsLeft} s more; then ${next.code})`);
203
253
  }));
204
254
  server.registerTool('fake_rule', {
205
255
  title: 'Make a provider misbehave',
@@ -337,11 +387,12 @@ export function buildServer(h, runs = createRuns(h)) {
337
387
  description: 'Do one thing on a screen: click {role, name}, fill {label, text}, select {label, option}, press {key}, ' +
338
388
  'draw {label} (pointer strokes, e.g. a signature pad; strokes default to a signature), drag {role, name} ' +
339
389
  'onto another element (to {role, name, at}) or by a distance (by {x, y} CSS px), held at grab (fractions ' +
340
- 'of its box: an edge to resize; default its middle), or goto {path} (a ' +
390
+ 'of its box: an edge to resize; default its middle; a drop off the screen is scrolled to on the way), ' +
391
+ 'scroll {role, name} into view, or by {x, y} / to top|bottom over that panel (or the page), or goto {path} (a ' +
341
392
  `path, or a full link on the ${noun}’s host as a text gives it). Elements are found by accessible role and ` +
342
393
  'name, as in screen_read; of several, the one in the open dialog is taken, else each is described for index. ' +
343
- 'Something behind a modal dialog is refused with the dialog’s name. Returns what it did, what changed ' +
344
- '(dialogs, status lines, the address), what the server did (yours, with what it set off, and any failure ' +
394
+ 'Something behind a modal dialog is refused with the dialog’s name. Waits for the requests it started (up ' +
395
+ 'to 15 s). Returns what it did, what changed (dialogs, status lines, headings, the address), what the server did (yours, with what it set off, and any failure ' +
345
396
  `in the ${noun} since the last action), requests refused, then the screen as it is. Refused while a ` +
346
397
  'person has the screen.',
347
398
  inputSchema: {
@@ -396,6 +447,21 @@ export function buildServer(h, runs = createRuns(h)) {
396
447
  .optional()
397
448
  .describe('Or move it this far, in CSS pixels (screen_image is in them)'),
398
449
  }),
450
+ z.object({
451
+ kind: z.literal('scroll'),
452
+ role: z.string().optional(),
453
+ name: z.string().optional(),
454
+ exact: z.boolean().optional(),
455
+ index: z.int().optional(),
456
+ by: z
457
+ .object({ x: z.number().min(-20000).max(20000), y: z.number().min(-20000).max(20000) })
458
+ .optional()
459
+ .describe('Turn the wheel this far, in CSS pixels, over the element given (a panel) or the page'),
460
+ to: z
461
+ .enum(['top', 'bottom'])
462
+ .optional()
463
+ .describe('Or to its top or bottom. With neither, the element given is brought into view; with no element either, the page goes to its bottom'),
464
+ }),
399
465
  z.object({ kind: z.literal('press'), key: z.string() }),
400
466
  z.object({ kind: z.literal('goto'), path: z.string() }),
401
467
  z.object({
@@ -439,6 +505,28 @@ export function buildServer(h, runs = createRuns(h)) {
439
505
  : { mode });
440
506
  return text(`${screen}: network ${mode}${match ? ` for ${match}` : ''}`);
441
507
  }));
508
+ server.registerTool('screen_requests', {
509
+ title: 'Read a screen’s requests',
510
+ description: 'What a screen asked its servers for, newest last: method, address, status and time, and with bodies the ' +
511
+ 'start of what was sent and what came back. A request still open (a live stream, a long poll) says so. ' +
512
+ 'The readout’s "refused:" lines give each one’s #number. Pages’ scripts, styles, images and fonts are left ' +
513
+ 'out unless all is set. Headers are never kept, and secrets in addresses and bodies are redacted.',
514
+ annotations: { readOnlyHint: true },
515
+ inputSchema: {
516
+ screen: z.string(),
517
+ match: z.string().min(1).optional().describe('Only addresses containing this, e.g. "/api/live"'),
518
+ after: z.int().min(0).optional().describe('Only requests after this #number'),
519
+ bodies: z.boolean().optional().describe('Show what was sent and what came back; default no'),
520
+ all: z.boolean().optional().describe('Include the page’s own scripts, styles, images and fonts'),
521
+ limit: z.int().min(1).max(REQUESTS_KEPT).optional().describe('The most recent this many; default 40'),
522
+ },
523
+ }, async ({ screen, bodies, ...q }) => guard(async () => {
524
+ const got = h.requests(screen, q);
525
+ const now = Date.now();
526
+ return text(got.length
527
+ ? got.map((r) => requestLine(r, now, bodies ?? false)).join('\n')
528
+ : `${screen}: no requests${q.match ? ` matching "${q.match}"` : ''}${q.after !== undefined ? ` after #${q.after}` : ''}`);
529
+ }));
442
530
  server.registerTool('scenario_list', {
443
531
  title: 'List scenarios',
444
532
  description: 'The written scenarios a run can play: what each tests.',