@tangle-network/agent-app 0.49.25 → 0.49.27

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.
@@ -24,7 +24,7 @@ export interface HostedAgentConfig {
24
24
  profile: AgentProfile;
25
25
  /** Backend harness type, such as `opencode`; the runtime default when omitted. */
26
26
  harness?: string;
27
- /** The owner's own phone (E.164), the line's first member. */
27
+ /** The owner's address on the chosen transport: E.164 phone or email. */
28
28
  owner: string;
29
29
  /** Texts Hub answers per person per UTC day. Default 20. */
30
30
  freeTurnsPerDay?: number;
@@ -76,9 +76,12 @@ export declare class HostedAgentError extends Error {
76
76
  }
77
77
  export declare function createHostedAgent(config: HostedAgentConfig): {
78
78
  /**
79
- * Attach an Inkbox iMessage identity, connected to Hub under the
80
- * developer's account, as this agent's line: the owner and anyone who
81
- * texts it each get their own box and thread. With `voice`, calls to the
79
+ * Attach a Hub connection the developer owns as this agent's line:
80
+ * an Inkbox iMessage identity (default), its email mailbox, or a Linq
81
+ * WhatsApp number (`phoneNumberId`). The owner's address must match the
82
+ * transport. In `shared` mode (default) the owner and anyone who texts it
83
+ * each get their own box and thread; `personal` admits the owner only.
84
+ * With `voice` (iMessage lines only), calls to the
82
85
  * line reach the caller's box and thread through that ph0ny agent; Hub
83
86
  * admits only members, so a caller texts once before calling. Safe to
84
87
  * repeat with the same config. Hub refuses a changed profile, box or
@@ -89,6 +92,10 @@ export declare function createHostedAgent(config: HostedAgentConfig): {
89
92
  * route would also answer.
90
93
  */
91
94
  attachLine(connectionId: string, options?: {
95
+ transport?: 'imessage' | 'email' | 'whatsapp';
96
+ mode?: 'personal' | 'shared';
97
+ /** Required for WhatsApp because one Linq connection may own several numbers. */
98
+ phoneNumberId?: string;
92
99
  voice?: LineVoiceOptions;
93
100
  }): Promise<Line>;
94
101
  };
@@ -27,6 +27,7 @@ function conversationProfile(profile) {
27
27
  }
28
28
  var PERSON = { context: "own", tools: "act" };
29
29
  var E164 = /^\+[1-9]\d{6,14}$/;
30
+ var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
30
31
  var HostedAgentError = class extends Error {
31
32
  constructor(code, message) {
32
33
  super(message);
@@ -36,7 +37,7 @@ var HostedAgentError = class extends Error {
36
37
  code;
37
38
  };
38
39
  function createHostedAgent(config) {
39
- if (!E164.test(config.owner)) throw new HostedAgentError("owner_not_e164", "owner must be an E.164 phone number, such as +15550100001.");
40
+ if (!E164.test(config.owner) && !EMAIL.test(config.owner)) throw new HostedAgentError("owner_not_e164", "owner must be an E.164 phone number or email address.");
40
41
  const policy = { ...DEFAULT_BOX_POLICY, ...config.box };
41
42
  const sandbox = new Sandbox({ apiKey: config.apiKey, baseUrl: config.sandboxUrl ?? "https://sandbox.tangle.tools", timeoutMs: 2e4 });
42
43
  const backend = { ...config.harness ? { type: config.harness } : {}, profile: conversationProfile(config.profile) };
@@ -50,9 +51,12 @@ function createHostedAgent(config) {
50
51
  };
51
52
  return {
52
53
  /**
53
- * Attach an Inkbox iMessage identity, connected to Hub under the
54
- * developer's account, as this agent's line: the owner and anyone who
55
- * texts it each get their own box and thread. With `voice`, calls to the
54
+ * Attach a Hub connection the developer owns as this agent's line:
55
+ * an Inkbox iMessage identity (default), its email mailbox, or a Linq
56
+ * WhatsApp number (`phoneNumberId`). The owner's address must match the
57
+ * transport. In `shared` mode (default) the owner and anyone who texts it
58
+ * each get their own box and thread; `personal` admits the owner only.
59
+ * With `voice` (iMessage lines only), calls to the
56
60
  * line reach the caller's box and thread through that ph0ny agent; Hub
57
61
  * admits only members, so a caller texts once before calling. Safe to
58
62
  * repeat with the same config. Hub refuses a changed profile, box or
@@ -63,13 +67,27 @@ function createHostedAgent(config) {
63
67
  * route would also answer.
64
68
  */
65
69
  async attachLine(connectionId, options = {}) {
66
- const line = await sandbox.lines.fromConnection({ connectionId, transport: "imessage", clientReference: "hosted-agent" });
70
+ const transport = options.transport ?? "imessage";
71
+ const mode = options.mode ?? "shared";
72
+ if (transport === "email" && !EMAIL.test(config.owner))
73
+ throw new HostedAgentError("owner_transport_mismatch", "email lines require an email owner address.");
74
+ if (transport !== "email" && !E164.test(config.owner))
75
+ throw new HostedAgentError("owner_transport_mismatch", `${transport} lines require an E.164 owner address.`);
76
+ if (transport === "whatsapp" && !options.phoneNumberId)
77
+ throw new HostedAgentError("phone_number_required", "WhatsApp lines require phoneNumberId.");
78
+ if (transport !== "whatsapp" && options.phoneNumberId)
79
+ throw new HostedAgentError("phone_number_not_allowed", "phoneNumberId is only valid for WhatsApp lines.");
80
+ if (options.voice && transport !== "imessage")
81
+ throw new HostedAgentError("voice_transport_unsupported", "Voice is supported only on iMessage lines.");
82
+ const line = await sandbox.lines.fromConnection(
83
+ transport === "whatsapp" ? { connectionId, transport, phoneNumberId: options.phoneNumberId, clientReference: "hosted-agent" } : { connectionId, transport, clientReference: "hosted-agent" }
84
+ );
67
85
  await sandbox.lines.attach({
68
86
  number: line.id,
69
- mode: "shared",
87
+ mode,
70
88
  members: [{ address: config.owner, role: "owner" }],
71
- unknownSenders: "guest",
72
- roles: { owner: PERSON, guest: PERSON },
89
+ unknownSenders: mode === "shared" ? "guest" : "reject",
90
+ roles: mode === "shared" ? { owner: PERSON, guest: PERSON } : { owner: PERSON },
73
91
  respond: { kind: "agent", backend },
74
92
  limits: { turnsPerMemberPerDay: config.freeTurnsPerDay ?? 20 },
75
93
  instance: { keyPrefix: PERSON_KEY_PREFIX, create },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hosted-agent/index.ts"],"sourcesContent":["import type { AgentProfile, BackendConfig, LineVoiceOptions } from '@tangle-network/sandbox'\nimport { type Line, type LineInstanceCreate, Sandbox } from '@tangle-network/sandbox/core'\n\n/**\n * A hosted agent: people text or call a line, and each person is answered\n * from their own isolated sandbox. The developer's Tangle API key pays for\n * every box, model turn, reply and call.\n *\n * The platform does the work. {@link HostedAgent.attachLine} attaches the\n * line to Tangle Hub with one sandbox per person: Hub routes each text and\n * call to the person's box (the named instance {@link PERSON_KEY_PREFIX} plus\n * a hash of their number), keeps one thread per person, handles STOP and\n * START, counts each person's texts per day, and sends the reply. A call on\n * the line runs in the same box and thread, so text and voice share one memory.\n */\n\nexport interface HostedAgentConfig {\n /** The developer's Tangle API key. */\n apiKey: string\n /**\n * The persona every person's box runs. A profile without `model.default`\n * runs {@link DEFAULT_HOSTED_MODEL}, and a profile without `tools` runs with\n * {@link CONVERSATION_TOOLS_OFF} turned off. Set `tools` to choose your own,\n * for example on a harness that cannot turn those tools off.\n */\n profile: AgentProfile\n /** Backend harness type, such as `opencode`; the runtime default when omitted. */\n harness?: string\n /** The owner's own phone (E.164), the line's first member. */\n owner: string\n /** Texts Hub answers per person per UTC day. Default 20. */\n freeTurnsPerDay?: number\n box?: Partial<BoxPolicy>\n sandboxUrl?: string\n}\n\nexport interface BoxPolicy {\n cpuCores: number\n memoryMB: number\n diskGB: number\n idleTimeoutSeconds: number\n maxLifetimeSeconds: number\n deleteAfterStoppedSeconds: number\n /** Egress allow-list. The default reaches the model router only. */\n allowDomains: string[]\n}\n\n/**\n * Two cores and a 2 GB disk cost what one core and 10 GB cost: both bill the\n * platform's hourly floor. A person's box starts OpenCode on their first text\n * and after every idle stop, and that start is CPU-bound: on one core it took\n * 6.1 s after a resume and 8.4 s on a new box, on two cores 3.5 s and 3.4 s\n * (production, 2026-09-24). A disk no larger than the platform's warm seed\n * lets a new person's box be claimed from the warm pool: create took 2.1-2.7 s\n * instead of 5.3-7.0 s (2026-09-25).\n */\nexport const DEFAULT_BOX_POLICY: BoxPolicy = {\n cpuCores: 2, memoryMB: 2048, diskGB: 2,\n idleTimeoutSeconds: 600, maxLifetimeSeconds: 86_400, deleteAfterStoppedSeconds: 7 * 86_400,\n allowDomains: ['router.tangle.tools'],\n}\n\n/**\n * Each person's box is the developer's named instance with this prefix. It is\n * the key this kit used before Hub routed its texts, so every existing\n * person keeps their box.\n */\nexport const PERSON_KEY_PREFIX = 'hosted:'\n\n/**\n * Harness tools a texting or calling assistant does not use. Their\n * descriptions present every turn as coding work: a shell, file search,\n * sub-agents, to-do lists, skills and web fetch. File read, write and edit\n * stay, so a persona can keep notes such as `memory.md`.\n */\nexport const CONVERSATION_TOOLS_OFF = ['bash', 'glob', 'grep', 'task', 'todowrite', 'webfetch', 'skill'] as const\n\n/**\n * The model for a profile without `model.default`. It gave the most useful\n * on-topic replies among four Router models on the same five texts and calls\n * (2026-09-23), within the latency of the others.\n */\nexport const DEFAULT_HOSTED_MODEL = 'openai/gpt-5.6-luna'\n\n/** The profile a person's box runs: the developer's profile over the conversation defaults. */\nfunction conversationProfile(profile: AgentProfile): AgentProfile {\n return {\n ...profile,\n model: { ...profile.model, default: profile.model?.default ?? DEFAULT_HOSTED_MODEL },\n // A profile that sets `tools` owns its tool set.\n ...(profile.tools ? {} : {\n tools: Object.fromEntries(CONVERSATION_TOOLS_OFF.map(tool => [tool, false])),\n // The sandbox's preview policy grants the shell unless its permission\n // is denied, so turning the tool off alone leaves the shell in place.\n permissions: { bash: 'deny' as const, ...profile.permissions },\n }),\n }\n}\n\n/** Each person runs in their own box, so no member shares a disk and each may use the persona's tools. */\nconst PERSON = { context: 'own', tools: 'act' } as const\nconst E164 = /^\\+[1-9]\\d{6,14}$/\n\nexport class HostedAgentError extends Error {\n constructor(readonly code: string, message: string) { super(message); this.name = 'HostedAgentError' }\n}\n\nexport function createHostedAgent(config: HostedAgentConfig) {\n if (!E164.test(config.owner)) throw new HostedAgentError('owner_not_e164', 'owner must be an E.164 phone number, such as +15550100001.')\n const policy = { ...DEFAULT_BOX_POLICY, ...config.box }\n const sandbox = new Sandbox({ apiKey: config.apiKey, baseUrl: config.sandboxUrl ?? 'https://sandbox.tangle.tools', timeoutMs: 20_000 })\n const backend: BackendConfig = { ...(config.harness ? { type: config.harness as BackendConfig['type'] } : {}), profile: conversationProfile(config.profile) }\n const create: LineInstanceCreate = {\n name: 'hosted-person',\n resources: { cpuCores: policy.cpuCores, memoryMB: policy.memoryMB, diskGB: policy.diskGB },\n egressPolicy: { mode: 'strict', allowDomains: policy.allowDomains, includeImplicitDomains: false },\n idleTimeoutSeconds: policy.idleTimeoutSeconds, maxLifetimeSeconds: policy.maxLifetimeSeconds,\n deleteAfterStoppedSeconds: policy.deleteAfterStoppedSeconds,\n }\n\n return {\n /**\n * Attach an Inkbox iMessage identity, connected to Hub under the\n * developer's account, as this agent's line: the owner and anyone who\n * texts it each get their own box and thread. With `voice`, calls to the\n * line reach the caller's box and thread through that ph0ny agent; Hub\n * admits only members, so a caller texts once before calling. Safe to\n * repeat with the same config. Hub refuses a changed profile, box or\n * limit on an attached line: detach it first\n * (`DELETE /v1/lines/:id/attachment`). Each person keeps their box and\n * its memory, and their thread starts over. Remove any Hub event\n * subscription on the connection first; Hub refuses a line that another\n * route would also answer.\n */\n async attachLine(connectionId: string, options: { voice?: LineVoiceOptions } = {}): Promise<Line> {\n const line = await sandbox.lines.fromConnection({ connectionId, transport: 'imessage', clientReference: 'hosted-agent' })\n await sandbox.lines.attach({\n number: line.id,\n mode: 'shared',\n members: [{ address: config.owner, role: 'owner' }],\n unknownSenders: 'guest',\n roles: { owner: PERSON, guest: PERSON },\n respond: { kind: 'agent', backend },\n limits: { turnsPerMemberPerDay: config.freeTurnsPerDay ?? 20 },\n instance: { keyPrefix: PERSON_KEY_PREFIX, create },\n clientReference: 'hosted-agent',\n })\n if (options.voice) await sandbox.lines.enableVoice(line.id, options.voice)\n return sandbox.lines.get(line.id)\n },\n }\n}\n\nexport type HostedAgent = ReturnType<typeof createHostedAgent>\n"],"mappings":";AACA,SAA6C,eAAe;AAuDrD,IAAM,qBAAgC;AAAA,EAC3C,UAAU;AAAA,EAAG,UAAU;AAAA,EAAM,QAAQ;AAAA,EACrC,oBAAoB;AAAA,EAAK,oBAAoB;AAAA,EAAQ,2BAA2B,IAAI;AAAA,EACpF,cAAc,CAAC,qBAAqB;AACtC;AAOO,IAAM,oBAAoB;AAQ1B,IAAM,yBAAyB,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,aAAa,YAAY,OAAO;AAOhG,IAAM,uBAAuB;AAGpC,SAAS,oBAAoB,SAAqC;AAChE,SAAO;AAAA,IACL,GAAG;AAAA,IACH,OAAO,EAAE,GAAG,QAAQ,OAAO,SAAS,QAAQ,OAAO,WAAW,qBAAqB;AAAA;AAAA,IAEnF,GAAI,QAAQ,QAAQ,CAAC,IAAI;AAAA,MACvB,OAAO,OAAO,YAAY,uBAAuB,IAAI,UAAQ,CAAC,MAAM,KAAK,CAAC,CAAC;AAAA;AAAA;AAAA,MAG3E,aAAa,EAAE,MAAM,QAAiB,GAAG,QAAQ,YAAY;AAAA,IAC/D;AAAA,EACF;AACF;AAGA,IAAM,SAAS,EAAE,SAAS,OAAO,OAAO,MAAM;AAC9C,IAAM,OAAO;AAEN,IAAM,mBAAN,cAA+B,MAAM;AAAA,EAC1C,YAAqB,MAAc,SAAiB;AAAE,UAAM,OAAO;AAA9C;AAAiD,SAAK,OAAO;AAAA,EAAmB;AAAA,EAAhF;AACvB;AAEO,SAAS,kBAAkB,QAA2B;AAC3D,MAAI,CAAC,KAAK,KAAK,OAAO,KAAK,EAAG,OAAM,IAAI,iBAAiB,kBAAkB,4DAA4D;AACvI,QAAM,SAAS,EAAE,GAAG,oBAAoB,GAAG,OAAO,IAAI;AACtD,QAAM,UAAU,IAAI,QAAQ,EAAE,QAAQ,OAAO,QAAQ,SAAS,OAAO,cAAc,gCAAgC,WAAW,IAAO,CAAC;AACtI,QAAM,UAAyB,EAAE,GAAI,OAAO,UAAU,EAAE,MAAM,OAAO,QAAiC,IAAI,CAAC,GAAI,SAAS,oBAAoB,OAAO,OAAO,EAAE;AAC5J,QAAM,SAA6B;AAAA,IACjC,MAAM;AAAA,IACN,WAAW,EAAE,UAAU,OAAO,UAAU,UAAU,OAAO,UAAU,QAAQ,OAAO,OAAO;AAAA,IACzF,cAAc,EAAE,MAAM,UAAU,cAAc,OAAO,cAAc,wBAAwB,MAAM;AAAA,IACjG,oBAAoB,OAAO;AAAA,IAAoB,oBAAoB,OAAO;AAAA,IAC1E,2BAA2B,OAAO;AAAA,EACpC;AAEA,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAcL,MAAM,WAAW,cAAsB,UAAwC,CAAC,GAAkB;AAChG,YAAM,OAAO,MAAM,QAAQ,MAAM,eAAe,EAAE,cAAc,WAAW,YAAY,iBAAiB,eAAe,CAAC;AACxH,YAAM,QAAQ,MAAM,OAAO;AAAA,QACzB,QAAQ,KAAK;AAAA,QACb,MAAM;AAAA,QACN,SAAS,CAAC,EAAE,SAAS,OAAO,OAAO,MAAM,QAAQ,CAAC;AAAA,QAClD,gBAAgB;AAAA,QAChB,OAAO,EAAE,OAAO,QAAQ,OAAO,OAAO;AAAA,QACtC,SAAS,EAAE,MAAM,SAAS,QAAQ;AAAA,QAClC,QAAQ,EAAE,sBAAsB,OAAO,mBAAmB,GAAG;AAAA,QAC7D,UAAU,EAAE,WAAW,mBAAmB,OAAO;AAAA,QACjD,iBAAiB;AAAA,MACnB,CAAC;AACD,UAAI,QAAQ,MAAO,OAAM,QAAQ,MAAM,YAAY,KAAK,IAAI,QAAQ,KAAK;AACzE,aAAO,QAAQ,MAAM,IAAI,KAAK,EAAE;AAAA,IAClC;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/hosted-agent/index.ts"],"sourcesContent":["import type { AgentProfile, BackendConfig, LineVoiceOptions } from '@tangle-network/sandbox'\nimport { type Line, type LineInstanceCreate, Sandbox } from '@tangle-network/sandbox/core'\n\n/**\n * A hosted agent: people text or call a line, and each person is answered\n * from their own isolated sandbox. The developer's Tangle API key pays for\n * every box, model turn, reply and call.\n *\n * The platform does the work. {@link HostedAgent.attachLine} attaches the\n * line to Tangle Hub with one sandbox per person: Hub routes each text and\n * call to the person's box (the named instance {@link PERSON_KEY_PREFIX} plus\n * a hash of their number), keeps one thread per person, handles STOP and\n * START, counts each person's texts per day, and sends the reply. A call on\n * the line runs in the same box and thread, so text and voice share one memory.\n */\n\nexport interface HostedAgentConfig {\n /** The developer's Tangle API key. */\n apiKey: string\n /**\n * The persona every person's box runs. A profile without `model.default`\n * runs {@link DEFAULT_HOSTED_MODEL}, and a profile without `tools` runs with\n * {@link CONVERSATION_TOOLS_OFF} turned off. Set `tools` to choose your own,\n * for example on a harness that cannot turn those tools off.\n */\n profile: AgentProfile\n /** Backend harness type, such as `opencode`; the runtime default when omitted. */\n harness?: string\n /** The owner's address on the chosen transport: E.164 phone or email. */\n owner: string\n /** Texts Hub answers per person per UTC day. Default 20. */\n freeTurnsPerDay?: number\n box?: Partial<BoxPolicy>\n sandboxUrl?: string\n}\n\nexport interface BoxPolicy {\n cpuCores: number\n memoryMB: number\n diskGB: number\n idleTimeoutSeconds: number\n maxLifetimeSeconds: number\n deleteAfterStoppedSeconds: number\n /** Egress allow-list. The default reaches the model router only. */\n allowDomains: string[]\n}\n\n/**\n * Two cores and a 2 GB disk cost what one core and 10 GB cost: both bill the\n * platform's hourly floor. A person's box starts OpenCode on their first text\n * and after every idle stop, and that start is CPU-bound: on one core it took\n * 6.1 s after a resume and 8.4 s on a new box, on two cores 3.5 s and 3.4 s\n * (production, 2026-09-24). A disk no larger than the platform's warm seed\n * lets a new person's box be claimed from the warm pool: create took 2.1-2.7 s\n * instead of 5.3-7.0 s (2026-09-25).\n */\nexport const DEFAULT_BOX_POLICY: BoxPolicy = {\n cpuCores: 2, memoryMB: 2048, diskGB: 2,\n idleTimeoutSeconds: 600, maxLifetimeSeconds: 86_400, deleteAfterStoppedSeconds: 7 * 86_400,\n allowDomains: ['router.tangle.tools'],\n}\n\n/**\n * Each person's box is the developer's named instance with this prefix. It is\n * the key this kit used before Hub routed its texts, so every existing\n * person keeps their box.\n */\nexport const PERSON_KEY_PREFIX = 'hosted:'\n\n/**\n * Harness tools a texting or calling assistant does not use. Their\n * descriptions present every turn as coding work: a shell, file search,\n * sub-agents, to-do lists, skills and web fetch. File read, write and edit\n * stay, so a persona can keep notes such as `memory.md`.\n */\nexport const CONVERSATION_TOOLS_OFF = ['bash', 'glob', 'grep', 'task', 'todowrite', 'webfetch', 'skill'] as const\n\n/**\n * The model for a profile without `model.default`. It gave the most useful\n * on-topic replies among four Router models on the same five texts and calls\n * (2026-09-23), within the latency of the others.\n */\nexport const DEFAULT_HOSTED_MODEL = 'openai/gpt-5.6-luna'\n\n/** The profile a person's box runs: the developer's profile over the conversation defaults. */\nfunction conversationProfile(profile: AgentProfile): AgentProfile {\n return {\n ...profile,\n model: { ...profile.model, default: profile.model?.default ?? DEFAULT_HOSTED_MODEL },\n // A profile that sets `tools` owns its tool set.\n ...(profile.tools ? {} : {\n tools: Object.fromEntries(CONVERSATION_TOOLS_OFF.map(tool => [tool, false])),\n // The sandbox's preview policy grants the shell unless its permission\n // is denied, so turning the tool off alone leaves the shell in place.\n permissions: { bash: 'deny' as const, ...profile.permissions },\n }),\n }\n}\n\n/** Each person runs in their own box, so no member shares a disk and each may use the persona's tools. */\nconst PERSON = { context: 'own', tools: 'act' } as const\nconst E164 = /^\\+[1-9]\\d{6,14}$/\nconst EMAIL = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\nexport class HostedAgentError extends Error {\n constructor(readonly code: string, message: string) { super(message); this.name = 'HostedAgentError' }\n}\n\nexport function createHostedAgent(config: HostedAgentConfig) {\n if (!E164.test(config.owner) && !EMAIL.test(config.owner)) throw new HostedAgentError('owner_not_e164', 'owner must be an E.164 phone number or email address.')\n const policy = { ...DEFAULT_BOX_POLICY, ...config.box }\n const sandbox = new Sandbox({ apiKey: config.apiKey, baseUrl: config.sandboxUrl ?? 'https://sandbox.tangle.tools', timeoutMs: 20_000 })\n const backend: BackendConfig = { ...(config.harness ? { type: config.harness as BackendConfig['type'] } : {}), profile: conversationProfile(config.profile) }\n const create: LineInstanceCreate = {\n name: 'hosted-person',\n resources: { cpuCores: policy.cpuCores, memoryMB: policy.memoryMB, diskGB: policy.diskGB },\n egressPolicy: { mode: 'strict', allowDomains: policy.allowDomains, includeImplicitDomains: false },\n idleTimeoutSeconds: policy.idleTimeoutSeconds, maxLifetimeSeconds: policy.maxLifetimeSeconds,\n deleteAfterStoppedSeconds: policy.deleteAfterStoppedSeconds,\n }\n\n return {\n /**\n * Attach a Hub connection the developer owns as this agent's line:\n * an Inkbox iMessage identity (default), its email mailbox, or a Linq\n * WhatsApp number (`phoneNumberId`). The owner's address must match the\n * transport. In `shared` mode (default) the owner and anyone who texts it\n * each get their own box and thread; `personal` admits the owner only.\n * With `voice` (iMessage lines only), calls to the\n * line reach the caller's box and thread through that ph0ny agent; Hub\n * admits only members, so a caller texts once before calling. Safe to\n * repeat with the same config. Hub refuses a changed profile, box or\n * limit on an attached line: detach it first\n * (`DELETE /v1/lines/:id/attachment`). Each person keeps their box and\n * its memory, and their thread starts over. Remove any Hub event\n * subscription on the connection first; Hub refuses a line that another\n * route would also answer.\n */\n async attachLine(connectionId: string, options: {\n transport?: 'imessage' | 'email' | 'whatsapp'\n mode?: 'personal' | 'shared'\n /** Required for WhatsApp because one Linq connection may own several numbers. */\n phoneNumberId?: string\n voice?: LineVoiceOptions\n } = {}): Promise<Line> {\n const transport = options.transport ?? 'imessage'\n const mode = options.mode ?? 'shared'\n if (transport === 'email' && !EMAIL.test(config.owner))\n throw new HostedAgentError('owner_transport_mismatch', 'email lines require an email owner address.')\n if (transport !== 'email' && !E164.test(config.owner))\n throw new HostedAgentError('owner_transport_mismatch', `${transport} lines require an E.164 owner address.`)\n if (transport === 'whatsapp' && !options.phoneNumberId)\n throw new HostedAgentError('phone_number_required', 'WhatsApp lines require phoneNumberId.')\n if (transport !== 'whatsapp' && options.phoneNumberId)\n throw new HostedAgentError('phone_number_not_allowed', 'phoneNumberId is only valid for WhatsApp lines.')\n if (options.voice && transport !== 'imessage')\n throw new HostedAgentError('voice_transport_unsupported', 'Voice is supported only on iMessage lines.')\n const line = await sandbox.lines.fromConnection(\n transport === 'whatsapp'\n ? { connectionId, transport, phoneNumberId: options.phoneNumberId!, clientReference: 'hosted-agent' }\n : { connectionId, transport, clientReference: 'hosted-agent' },\n )\n await sandbox.lines.attach({\n number: line.id,\n mode,\n members: [{ address: config.owner, role: 'owner' }],\n unknownSenders: mode === 'shared' ? 'guest' : 'reject',\n roles: mode === 'shared' ? { owner: PERSON, guest: PERSON } : { owner: PERSON },\n respond: { kind: 'agent', backend },\n limits: { turnsPerMemberPerDay: config.freeTurnsPerDay ?? 20 },\n instance: { keyPrefix: PERSON_KEY_PREFIX, create },\n clientReference: 'hosted-agent',\n })\n if (options.voice) await sandbox.lines.enableVoice(line.id, options.voice)\n return sandbox.lines.get(line.id)\n },\n }\n}\n\nexport type HostedAgent = ReturnType<typeof createHostedAgent>\n"],"mappings":"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","names":[]}
@@ -66,12 +66,18 @@ export interface ResolveSandboxClientCredentialsOptions {
66
66
  }
67
67
  /** Resolve sandbox client credentials based on environment and provided options asynchronously */
68
68
  export declare function resolveSandboxClientCredentials(options?: ResolveSandboxClientCredentialsOptions): Promise<SandboxClientCredentials>;
69
- /** Define configuration parameters for sandbox resource allocation and lifecycle management */
69
+ /**
70
+ * Configure sandbox resources and lifecycle limits.
71
+ * Set `advisory` for application defaults that the Sandbox API may clamp to account limits.
72
+ * Omit it when the requested allocation must be honored or rejected.
73
+ */
70
74
  export interface SandboxResourceConfig {
71
75
  image: string;
72
76
  cpuCores: number;
73
77
  memoryMB: number;
74
78
  diskGB: number;
79
+ /** Let the Sandbox API clamp these resource defaults to the account's limits. */
80
+ advisory?: boolean;
75
81
  maxLifetimeSeconds: number;
76
82
  idleTimeoutSeconds: number;
77
83
  }
@@ -99,7 +99,7 @@ import {
99
99
  workspaceSandboxRecoveryMessage,
100
100
  workspaceSandboxRecoveryRecommendedActions,
101
101
  writeProfileFilesToBox
102
- } from "../chunk-J42REVQJ.js";
102
+ } from "../chunk-GEHULTVU.js";
103
103
  import "../chunk-MCYJON3F.js";
104
104
  import "../chunk-LWSJK546.js";
105
105
  import "../chunk-FZOGDD2E.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-app",
3
- "version": "0.49.25",
3
+ "version": "0.49.27",
4
4
  "packageManager": "pnpm@11.24.0",
5
5
  "description": "Build agent applications with typed chat, tools, sandboxes, integrations, billing, and evaluation.",
6
6
  "keywords": [
@@ -545,13 +545,13 @@
545
545
  "@storybook/react-vite": "^10.5.10",
546
546
  "@tailwindcss/postcss": "^4.3.3",
547
547
  "@tangle-network/agent-docs": "0.2.1",
548
- "@tangle-network/agent-eval": "0.190.1",
548
+ "@tangle-network/agent-eval": "0.197.0",
549
549
  "@tangle-network/agent-gateway": "0.11.4",
550
550
  "@tangle-network/agent-integrations": "0.55.0",
551
551
  "@tangle-network/agent-interface": "2.12.0",
552
552
  "@tangle-network/agent-knowledge": "17.1.5",
553
553
  "@tangle-network/agent-profile-materialize": "0.20.1",
554
- "@tangle-network/agent-runtime": "0.274.0",
554
+ "@tangle-network/agent-runtime": "0.277.0",
555
555
  "@tangle-network/brand": "1.5.0",
556
556
  "@tangle-network/hub-sdk": "0.19.0",
557
557
  "@tangle-network/sandbox": "0.53.0",
@@ -607,12 +607,12 @@
607
607
  "peerDependencies": {
608
608
  "@firecrawl/pdf-inspector-wasm": ">=0.1.3",
609
609
  "@huggingface/transformers": ">=3",
610
- "@tangle-network/agent-eval": ">=0.185.0 <0.191.0",
610
+ "@tangle-network/agent-eval": ">=0.185.0 <0.198.0",
611
611
  "@tangle-network/agent-integrations": ">=0.54.2 <0.56.0",
612
612
  "@tangle-network/agent-interface": "^2.11.0",
613
613
  "@tangle-network/agent-knowledge": "^17.1.2",
614
614
  "@tangle-network/agent-profile-materialize": ">=0.20.1 <0.21.0",
615
- "@tangle-network/agent-runtime": ">=0.262.0 <0.275.0",
615
+ "@tangle-network/agent-runtime": ">=0.262.0 <0.278.0",
616
616
  "@tangle-network/brand": ">=1.5.0",
617
617
  "@tangle-network/hub-sdk": ">=0.19.0 <0.20.0",
618
618
  "@tangle-network/sandbox": ">=0.45.0 <0.48.0 || ^0.49.0 || ^0.50.0 || ^0.51.0 || ^0.52.0 || ^0.53.0",