@tangle-network/agent-app 0.49.31 → 0.49.32
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.
|
@@ -1,35 +1,40 @@
|
|
|
1
|
-
import type { AgentProfile, LineVoiceOptions } from '@tangle-network/sandbox';
|
|
2
|
-
import { type Line } from '@tangle-network/sandbox/core';
|
|
1
|
+
import type { AgentProfile, BackendConfig, LineVoiceOptions } from '@tangle-network/sandbox';
|
|
2
|
+
import { type Line, Sandbox } from '@tangle-network/sandbox/core';
|
|
3
|
+
/** Native Hub options. The kit does not keep a second membership or execution store. */
|
|
4
|
+
type NativeAttachment = Parameters<Sandbox['lines']['attach']>[0];
|
|
5
|
+
export type HostedAgentAttachment = Partial<Omit<NativeAttachment, 'number' | 'mode' | 'respond'>>;
|
|
6
|
+
export type HostedAgentTransport = 'imessage' | 'whatsapp' | 'email';
|
|
7
|
+
export interface HostedAgentLineOptions {
|
|
8
|
+
transport?: HostedAgentTransport;
|
|
9
|
+
mode?: 'personal' | 'shared';
|
|
10
|
+
/** Required when creating a WhatsApp line on a connection with several numbers. */
|
|
11
|
+
phoneNumberId?: string;
|
|
12
|
+
voice?: LineVoiceOptions;
|
|
13
|
+
}
|
|
3
14
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* every box, model turn, reply and call.
|
|
7
|
-
*
|
|
8
|
-
* The platform does the work. {@link HostedAgent.attachLine} attaches the
|
|
9
|
-
* line to Tangle Hub with one sandbox per person: Hub routes each text and
|
|
10
|
-
* call to the person's box (the named instance {@link PERSON_KEY_PREFIX} plus
|
|
11
|
-
* a hash of their number), keeps one thread per person, handles STOP and
|
|
12
|
-
* START, counts each person's texts per day, and sends the reply. A call on
|
|
13
|
-
* the line runs in the same box and thread, so text and voice share one memory.
|
|
15
|
+
* Hub owns delivery, member threads, consent and execution. A host keeps its
|
|
16
|
+
* product catalog and billing policy, then passes that policy to this kit.
|
|
14
17
|
*/
|
|
15
18
|
export interface HostedAgentConfig {
|
|
16
|
-
/**
|
|
17
|
-
apiKey
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
* runs {@link DEFAULT_HOSTED_MODEL}, and a profile without `tools` runs with
|
|
21
|
-
* {@link CONVERSATION_TOOLS_OFF} turned off. Set `tools` to choose your own,
|
|
22
|
-
* for example on a harness that cannot turn those tools off.
|
|
23
|
-
*/
|
|
19
|
+
/** Developer's Tangle API key. Required unless an authenticated client is supplied. */
|
|
20
|
+
apiKey?: string;
|
|
21
|
+
/** Reuse the host's authenticated SDK client, including its timeout and tracing. */
|
|
22
|
+
client?: Pick<Sandbox, 'lines'>;
|
|
24
23
|
profile: AgentProfile;
|
|
25
|
-
/** Backend harness type, such as `opencode`; the runtime default when omitted. */
|
|
26
24
|
harness?: string;
|
|
27
|
-
/**
|
|
25
|
+
/** A saved release can supply its complete, already-validated backend unchanged. */
|
|
26
|
+
backend?: BackendConfig;
|
|
27
|
+
/** E.164 phone, or an email address (also accepted as an iMessage Apple ID). */
|
|
28
28
|
owner: string;
|
|
29
|
-
/** Texts Hub answers per person per UTC day. Default 20. */
|
|
30
29
|
freeTurnsPerDay?: number;
|
|
31
30
|
box?: Partial<BoxPolicy>;
|
|
32
31
|
sandboxUrl?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Host-selected members, roles, limits, instance namespace and reference.
|
|
34
|
+
* Use a distinct instance.keyPrefix per assistant to keep their disks apart.
|
|
35
|
+
* Supplying this never bypasses Hub's validation or owner checks.
|
|
36
|
+
*/
|
|
37
|
+
attachment?: HostedAgentAttachment;
|
|
33
38
|
}
|
|
34
39
|
export interface BoxPolicy {
|
|
35
40
|
cpuCores: number;
|
|
@@ -38,37 +43,12 @@ export interface BoxPolicy {
|
|
|
38
43
|
idleTimeoutSeconds: number;
|
|
39
44
|
maxLifetimeSeconds: number;
|
|
40
45
|
deleteAfterStoppedSeconds: number;
|
|
41
|
-
/** Egress allow-list. The default reaches the model router only. */
|
|
42
46
|
allowDomains: string[];
|
|
43
47
|
}
|
|
44
|
-
/**
|
|
45
|
-
* Two cores and a 2 GB disk cost what one core and 10 GB cost: both bill the
|
|
46
|
-
* platform's hourly floor. A person's box starts OpenCode on their first text
|
|
47
|
-
* and after every idle stop, and that start is CPU-bound: on one core it took
|
|
48
|
-
* 6.1 s after a resume and 8.4 s on a new box, on two cores 3.5 s and 3.4 s
|
|
49
|
-
* (production, 2026-09-24). A disk no larger than the platform's warm seed
|
|
50
|
-
* lets a new person's box be claimed from the warm pool: create took 2.1-2.7 s
|
|
51
|
-
* instead of 5.3-7.0 s (2026-09-25).
|
|
52
|
-
*/
|
|
53
48
|
export declare const DEFAULT_BOX_POLICY: BoxPolicy;
|
|
54
|
-
/**
|
|
55
|
-
* Each person's box is the developer's named instance with this prefix. It is
|
|
56
|
-
* the key this kit used before Hub routed its texts, so every existing
|
|
57
|
-
* person keeps their box.
|
|
58
|
-
*/
|
|
49
|
+
/** Legacy default. Hosts running several assistants should supply their own namespace. */
|
|
59
50
|
export declare const PERSON_KEY_PREFIX = "hosted:";
|
|
60
|
-
/**
|
|
61
|
-
* Harness tools a texting or calling assistant does not use. Their
|
|
62
|
-
* descriptions present every turn as coding work: a shell, file search,
|
|
63
|
-
* sub-agents, to-do lists, skills and web fetch. File read, write and edit
|
|
64
|
-
* stay, so a persona can keep notes such as `memory.md`.
|
|
65
|
-
*/
|
|
66
51
|
export declare const CONVERSATION_TOOLS_OFF: readonly ['bash', 'glob', 'grep', 'task', 'todowrite', 'webfetch', 'skill'];
|
|
67
|
-
/**
|
|
68
|
-
* The model for a profile without `model.default`. It gave the most useful
|
|
69
|
-
* on-topic replies among four Router models on the same five texts and calls
|
|
70
|
-
* (2026-09-23), within the latency of the others.
|
|
71
|
-
*/
|
|
72
52
|
export declare const DEFAULT_HOSTED_MODEL = "openai/gpt-5.6-luna";
|
|
73
53
|
export declare class HostedAgentError extends Error {
|
|
74
54
|
readonly code: string;
|
|
@@ -76,27 +56,18 @@ export declare class HostedAgentError extends Error {
|
|
|
76
56
|
}
|
|
77
57
|
export declare function createHostedAgent(config: HostedAgentConfig): {
|
|
78
58
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* (`DELETE /v1/lines/:id/attachment`). Each person keeps their box and
|
|
90
|
-
* its memory, and their thread starts over. Remove any Hub event
|
|
91
|
-
* subscription on the connection first; Hub refuses a line that another
|
|
92
|
-
* route would also answer.
|
|
59
|
+
* Create an address from an owned connection and attach it. Shared email
|
|
60
|
+
* admits declared members only. Hub authenticates each mailbox by reply.
|
|
61
|
+
* Other shared transports can admit isolated guests. Personal admits one
|
|
62
|
+
* owner. Remove competing subscriptions before calling this method.
|
|
63
|
+
*/
|
|
64
|
+
attachLine(connectionId: string, options?: HostedAgentLineOptions): Promise<Line>;
|
|
65
|
+
/**
|
|
66
|
+
* Attach an already-created owner-scoped line. Used during a host cutover
|
|
67
|
+
* so the line id, connection, named-instance namespace and payer survive.
|
|
68
|
+
* A 409 remains a 409. This method never detaches a competing attachment.
|
|
93
69
|
*/
|
|
94
|
-
|
|
95
|
-
transport?: 'imessage' | 'email' | 'whatsapp';
|
|
96
|
-
mode?: 'personal' | 'shared';
|
|
97
|
-
/** Required for WhatsApp because one Linq connection may own several numbers. */
|
|
98
|
-
phoneNumberId?: string;
|
|
99
|
-
voice?: LineVoiceOptions;
|
|
100
|
-
}): Promise<Line>;
|
|
70
|
+
attachExistingLine(lineId: string, options?: HostedAgentLineOptions): Promise<Line>;
|
|
101
71
|
};
|
|
102
72
|
export type HostedAgent = ReturnType<typeof createHostedAgent>;
|
|
73
|
+
export {};
|
|
@@ -16,11 +16,8 @@ function conversationProfile(profile) {
|
|
|
16
16
|
return {
|
|
17
17
|
...profile,
|
|
18
18
|
model: { ...profile.model, default: profile.model?.default ?? DEFAULT_HOSTED_MODEL },
|
|
19
|
-
// A profile that sets `tools` owns its tool set.
|
|
20
19
|
...profile.tools ? {} : {
|
|
21
20
|
tools: Object.fromEntries(CONVERSATION_TOOLS_OFF.map((tool) => [tool, false])),
|
|
22
|
-
// The sandbox's preview policy grants the shell unless its permission
|
|
23
|
-
// is denied, so turning the tool off alone leaves the shell in place.
|
|
24
21
|
permissions: { bash: "deny", ...profile.permissions }
|
|
25
22
|
}
|
|
26
23
|
};
|
|
@@ -37,10 +34,16 @@ var HostedAgentError = class extends Error {
|
|
|
37
34
|
code;
|
|
38
35
|
};
|
|
39
36
|
function createHostedAgent(config) {
|
|
40
|
-
if (!E164.test(config.owner) && !EMAIL.test(config.owner))
|
|
37
|
+
if (!E164.test(config.owner) && !EMAIL.test(config.owner))
|
|
38
|
+
throw new HostedAgentError("owner_not_e164", "owner must be an E.164 phone number or email address.");
|
|
39
|
+
if (!config.client && !config.apiKey)
|
|
40
|
+
throw new HostedAgentError("credential_required", "Supply an API key or an authenticated Sandbox client.");
|
|
41
41
|
const policy = { ...DEFAULT_BOX_POLICY, ...config.box };
|
|
42
|
-
const sandbox = new Sandbox({ apiKey: config.apiKey, baseUrl: config.sandboxUrl ?? "https://sandbox.tangle.tools", timeoutMs: 2e4 });
|
|
43
|
-
const backend =
|
|
42
|
+
const sandbox = config.client ?? new Sandbox({ apiKey: config.apiKey, baseUrl: config.sandboxUrl ?? "https://sandbox.tangle.tools", timeoutMs: 2e4 });
|
|
43
|
+
const backend = config.backend ?? {
|
|
44
|
+
...config.harness ? { type: config.harness } : {},
|
|
45
|
+
profile: conversationProfile(config.profile)
|
|
46
|
+
};
|
|
44
47
|
const create = {
|
|
45
48
|
name: "hosted-person",
|
|
46
49
|
resources: { cpuCores: policy.cpuCores, memoryMB: policy.memoryMB, diskGB: policy.diskGB },
|
|
@@ -49,52 +52,77 @@ function createHostedAgent(config) {
|
|
|
49
52
|
maxLifetimeSeconds: policy.maxLifetimeSeconds,
|
|
50
53
|
deleteAfterStoppedSeconds: policy.deleteAfterStoppedSeconds
|
|
51
54
|
};
|
|
55
|
+
const members = config.attachment?.members ?? [{ address: config.owner, role: "owner" }];
|
|
56
|
+
function validate(transport, options, creating) {
|
|
57
|
+
if (!["imessage", "whatsapp", "email"].includes(transport))
|
|
58
|
+
throw new HostedAgentError("transport_unsupported", "Use an iMessage, WhatsApp or email line.");
|
|
59
|
+
if (options.mode !== void 0 && options.mode !== "personal" && options.mode !== "shared")
|
|
60
|
+
throw new HostedAgentError("mode_invalid", "mode must be personal or shared.");
|
|
61
|
+
for (const address of [config.owner, ...members.map((member) => member.address)]) {
|
|
62
|
+
const valid = transport === "email" ? EMAIL.test(address) : transport === "whatsapp" ? E164.test(address) : E164.test(address) || EMAIL.test(address);
|
|
63
|
+
if (!valid) throw new HostedAgentError("owner_transport_mismatch", `Member address does not match ${transport}.`);
|
|
64
|
+
}
|
|
65
|
+
if (creating && transport === "whatsapp" && !options.phoneNumberId)
|
|
66
|
+
throw new HostedAgentError("phone_number_required", "WhatsApp lines require phoneNumberId.");
|
|
67
|
+
if (transport !== "whatsapp" && options.phoneNumberId)
|
|
68
|
+
throw new HostedAgentError("phone_number_not_allowed", "phoneNumberId is only valid for WhatsApp lines.");
|
|
69
|
+
if (options.voice && transport !== "imessage")
|
|
70
|
+
throw new HostedAgentError("voice_transport_unsupported", "Voice is supported only on iMessage lines.");
|
|
71
|
+
if (transport === "email" && config.attachment?.unknownSenders && config.attachment.unknownSenders !== "reject")
|
|
72
|
+
throw new HostedAgentError("email_declared_members_required", "Email lines admit declared members only.");
|
|
73
|
+
}
|
|
74
|
+
async function attach(line, options) {
|
|
75
|
+
const transport = line.transport;
|
|
76
|
+
validate(transport, options, false);
|
|
77
|
+
if (options.transport && options.transport !== transport)
|
|
78
|
+
throw new HostedAgentError("line_transport_mismatch", "The existing line uses another transport.");
|
|
79
|
+
if (options.phoneNumberId && line.providerNumberId !== options.phoneNumberId)
|
|
80
|
+
throw new HostedAgentError("line_number_mismatch", "The existing line is pinned to another WhatsApp number.");
|
|
81
|
+
const retained = line.attachment?.status === "active" ? line.attachment : void 0;
|
|
82
|
+
if (retained?.unknownSenders === "onboard")
|
|
83
|
+
throw new HostedAgentError("line_policy_migration_required", "This line uses onboard admission. Manage it through Hub; the hosted-agent kit will not replace its policy.");
|
|
84
|
+
const mode = options.mode ?? retained?.mode ?? "shared";
|
|
85
|
+
const declaredMembers = config.attachment?.members ?? (retained ? (await sandbox.lines.members(line.id).list()).filter((member) => member.source === "declared").map((member) => ({ address: member.address, role: member.role, label: member.label ?? void 0 })) : members);
|
|
86
|
+
const keyPrefix = line.attachment?.instance?.keyPrefix ?? (line.clientReference === "hosted-agent" ? PERSON_KEY_PREFIX : `${PERSON_KEY_PREFIX}${line.id}:`);
|
|
87
|
+
await sandbox.lines.attach({
|
|
88
|
+
...config.attachment,
|
|
89
|
+
number: line.id,
|
|
90
|
+
mode,
|
|
91
|
+
members: declaredMembers,
|
|
92
|
+
unknownSenders: config.attachment?.unknownSenders ?? retained?.unknownSenders ?? (mode === "shared" && transport !== "email" ? "guest" : "reject"),
|
|
93
|
+
roles: config.attachment?.roles ?? retained?.roles ?? (mode === "shared" ? { owner: PERSON, member: PERSON, guest: PERSON } : { owner: PERSON }),
|
|
94
|
+
respond: { kind: "agent", backend },
|
|
95
|
+
limits: {
|
|
96
|
+
...retained?.limits,
|
|
97
|
+
turnsPerMemberPerDay: config.freeTurnsPerDay ?? retained?.limits.turnsPerMemberPerDay ?? 20,
|
|
98
|
+
...config.attachment?.limits
|
|
99
|
+
},
|
|
100
|
+
instance: config.attachment?.instance ?? retained?.instance ?? { keyPrefix, create },
|
|
101
|
+
clientReference: config.attachment?.clientReference ?? retained?.clientReference ?? "hosted-agent"
|
|
102
|
+
});
|
|
103
|
+
if (options.voice) await sandbox.lines.enableVoice(line.id, options.voice);
|
|
104
|
+
return sandbox.lines.get(line.id);
|
|
105
|
+
}
|
|
52
106
|
return {
|
|
53
107
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* each get their own box and thread; `personal` admits the owner only.
|
|
59
|
-
* With `voice` (iMessage lines only), calls to the
|
|
60
|
-
* line reach the caller's box and thread through that ph0ny agent; Hub
|
|
61
|
-
* admits only members, so a caller texts once before calling. Safe to
|
|
62
|
-
* repeat with the same config. Hub refuses a changed profile, box or
|
|
63
|
-
* limit on an attached line: detach it first
|
|
64
|
-
* (`DELETE /v1/lines/:id/attachment`). Each person keeps their box and
|
|
65
|
-
* its memory, and their thread starts over. Remove any Hub event
|
|
66
|
-
* subscription on the connection first; Hub refuses a line that another
|
|
67
|
-
* route would also answer.
|
|
108
|
+
* Create an address from an owned connection and attach it. Shared email
|
|
109
|
+
* admits declared members only. Hub authenticates each mailbox by reply.
|
|
110
|
+
* Other shared transports can admit isolated guests. Personal admits one
|
|
111
|
+
* owner. Remove competing subscriptions before calling this method.
|
|
68
112
|
*/
|
|
69
113
|
async attachLine(connectionId, options = {}) {
|
|
70
114
|
const transport = options.transport ?? "imessage";
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const line = await sandbox.lines.fromConnection(
|
|
83
|
-
transport === "whatsapp" ? { connectionId, transport, phoneNumberId: options.phoneNumberId, clientReference: "hosted-agent" } : { connectionId, transport, clientReference: "hosted-agent" }
|
|
84
|
-
);
|
|
85
|
-
await sandbox.lines.attach({
|
|
86
|
-
number: line.id,
|
|
87
|
-
mode,
|
|
88
|
-
members: [{ address: config.owner, role: "owner" }],
|
|
89
|
-
unknownSenders: mode === "shared" ? "guest" : "reject",
|
|
90
|
-
roles: mode === "shared" ? { owner: PERSON, guest: PERSON } : { owner: PERSON },
|
|
91
|
-
respond: { kind: "agent", backend },
|
|
92
|
-
limits: { turnsPerMemberPerDay: config.freeTurnsPerDay ?? 20 },
|
|
93
|
-
instance: { keyPrefix: PERSON_KEY_PREFIX, create },
|
|
94
|
-
clientReference: "hosted-agent"
|
|
95
|
-
});
|
|
96
|
-
if (options.voice) await sandbox.lines.enableVoice(line.id, options.voice);
|
|
97
|
-
return sandbox.lines.get(line.id);
|
|
115
|
+
validate(transport, options, true);
|
|
116
|
+
const line = await sandbox.lines.fromConnection(transport === "whatsapp" ? { connectionId, transport, phoneNumberId: options.phoneNumberId } : { connectionId, transport });
|
|
117
|
+
return attach(line, options);
|
|
118
|
+
},
|
|
119
|
+
/**
|
|
120
|
+
* Attach an already-created owner-scoped line. Used during a host cutover
|
|
121
|
+
* so the line id, connection, named-instance namespace and payer survive.
|
|
122
|
+
* A 409 remains a 409. This method never detaches a competing attachment.
|
|
123
|
+
*/
|
|
124
|
+
async attachExistingLine(lineId, options = {}) {
|
|
125
|
+
return attach(await sandbox.lines.get(lineId), options);
|
|
98
126
|
}
|
|
99
127
|
};
|
|
100
128
|
}
|
|
@@ -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 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":";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;AACb,IAAM,QAAQ;AAEP,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,KAAK,CAAC,MAAM,KAAK,OAAO,KAAK,EAAG,OAAM,IAAI,iBAAiB,kBAAkB,uDAAuD;AAC/J,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;AAAA;AAAA;AAAA,IAiBL,MAAM,WAAW,cAAsB,UAMnC,CAAC,GAAkB;AACrB,YAAM,YAAY,QAAQ,aAAa;AACvC,YAAM,OAAO,QAAQ,QAAQ;AAC7B,UAAI,cAAc,WAAW,CAAC,MAAM,KAAK,OAAO,KAAK;AACnD,cAAM,IAAI,iBAAiB,4BAA4B,6CAA6C;AACtG,UAAI,cAAc,WAAW,CAAC,KAAK,KAAK,OAAO,KAAK;AAClD,cAAM,IAAI,iBAAiB,4BAA4B,GAAG,SAAS,wCAAwC;AAC7G,UAAI,cAAc,cAAc,CAAC,QAAQ;AACvC,cAAM,IAAI,iBAAiB,yBAAyB,uCAAuC;AAC7F,UAAI,cAAc,cAAc,QAAQ;AACtC,cAAM,IAAI,iBAAiB,4BAA4B,iDAAiD;AAC1G,UAAI,QAAQ,SAAS,cAAc;AACjC,cAAM,IAAI,iBAAiB,+BAA+B,4CAA4C;AACxG,YAAM,OAAO,MAAM,QAAQ,MAAM;AAAA,QAC/B,cAAc,aACV,EAAE,cAAc,WAAW,eAAe,QAAQ,eAAgB,iBAAiB,eAAe,IAClG,EAAE,cAAc,WAAW,iBAAiB,eAAe;AAAA,MACjE;AACA,YAAM,QAAQ,MAAM,OAAO;AAAA,QACzB,QAAQ,KAAK;AAAA,QACb;AAAA,QACA,SAAS,CAAC,EAAE,SAAS,OAAO,OAAO,MAAM,QAAQ,CAAC;AAAA,QAClD,gBAAgB,SAAS,WAAW,UAAU;AAAA,QAC9C,OAAO,SAAS,WAAW,EAAE,OAAO,QAAQ,OAAO,OAAO,IAAI,EAAE,OAAO,OAAO;AAAA,QAC9E,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/** Native Hub options. The kit does not keep a second membership or execution store. */\ntype NativeAttachment = Parameters<Sandbox['lines']['attach']>[0]\nexport type HostedAgentAttachment = Partial<Omit<NativeAttachment, 'number' | 'mode' | 'respond'>>\nexport type HostedAgentTransport = 'imessage' | 'whatsapp' | 'email'\n\nexport interface HostedAgentLineOptions {\n transport?: HostedAgentTransport\n mode?: 'personal' | 'shared'\n /** Required when creating a WhatsApp line on a connection with several numbers. */\n phoneNumberId?: string\n voice?: LineVoiceOptions\n}\n\n/**\n * Hub owns delivery, member threads, consent and execution. A host keeps its\n * product catalog and billing policy, then passes that policy to this kit.\n */\nexport interface HostedAgentConfig {\n /** Developer's Tangle API key. Required unless an authenticated client is supplied. */\n apiKey?: string\n /** Reuse the host's authenticated SDK client, including its timeout and tracing. */\n client?: Pick<Sandbox, 'lines'>\n profile: AgentProfile\n harness?: string\n /** A saved release can supply its complete, already-validated backend unchanged. */\n backend?: BackendConfig\n /** E.164 phone, or an email address (also accepted as an iMessage Apple ID). */\n owner: string\n freeTurnsPerDay?: number\n box?: Partial<BoxPolicy>\n sandboxUrl?: string\n /**\n * Host-selected members, roles, limits, instance namespace and reference.\n * Use a distinct instance.keyPrefix per assistant to keep their disks apart.\n * Supplying this never bypasses Hub's validation or owner checks.\n */\n attachment?: HostedAgentAttachment\n}\n\nexport interface BoxPolicy {\n cpuCores: number\n memoryMB: number\n diskGB: number\n idleTimeoutSeconds: number\n maxLifetimeSeconds: number\n deleteAfterStoppedSeconds: number\n allowDomains: string[]\n}\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/** Legacy default. Hosts running several assistants should supply their own namespace. */\nexport const PERSON_KEY_PREFIX = 'hosted:'\nexport const CONVERSATION_TOOLS_OFF = ['bash', 'glob', 'grep', 'task', 'todowrite', 'webfetch', 'skill'] as const\nexport const DEFAULT_HOSTED_MODEL = 'openai/gpt-5.6-luna'\n\nfunction conversationProfile(profile: AgentProfile): AgentProfile {\n return {\n ...profile,\n model: { ...profile.model, default: profile.model?.default ?? DEFAULT_HOSTED_MODEL },\n ...(profile.tools ? {} : {\n tools: Object.fromEntries(CONVERSATION_TOOLS_OFF.map(tool => [tool, false])),\n permissions: { bash: 'deny' as const, ...profile.permissions },\n }),\n }\n}\n\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))\n throw new HostedAgentError('owner_not_e164', 'owner must be an E.164 phone number or email address.')\n if (!config.client && !config.apiKey)\n throw new HostedAgentError('credential_required', 'Supply an API key or an authenticated Sandbox client.')\n const policy = { ...DEFAULT_BOX_POLICY, ...config.box }\n const sandbox = config.client ?? new Sandbox({ apiKey: config.apiKey!, baseUrl: config.sandboxUrl ?? 'https://sandbox.tangle.tools', timeoutMs: 20_000 })\n const backend: BackendConfig = config.backend ?? {\n ...(config.harness ? { type: config.harness as BackendConfig['type'] } : {}),\n profile: conversationProfile(config.profile),\n }\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 const members = config.attachment?.members ?? [{ address: config.owner, role: 'owner' }]\n\n function validate(transport: HostedAgentTransport, options: HostedAgentLineOptions, creating: boolean) {\n if (!['imessage', 'whatsapp', 'email'].includes(transport))\n throw new HostedAgentError('transport_unsupported', 'Use an iMessage, WhatsApp or email line.')\n if (options.mode !== undefined && options.mode !== 'personal' && options.mode !== 'shared')\n throw new HostedAgentError('mode_invalid', 'mode must be personal or shared.')\n for (const address of [config.owner, ...members.map(member => member.address)]) {\n const valid = transport === 'email' ? EMAIL.test(address)\n : transport === 'whatsapp' ? E164.test(address) : E164.test(address) || EMAIL.test(address)\n if (!valid) throw new HostedAgentError('owner_transport_mismatch', `Member address does not match ${transport}.`)\n }\n if (creating && 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 if (transport === 'email' && config.attachment?.unknownSenders && config.attachment.unknownSenders !== 'reject')\n throw new HostedAgentError('email_declared_members_required', 'Email lines admit declared members only.')\n }\n\n async function attach(line: Line, options: HostedAgentLineOptions): Promise<Line> {\n const transport = line.transport as HostedAgentTransport\n validate(transport, options, false)\n if (options.transport && options.transport !== transport)\n throw new HostedAgentError('line_transport_mismatch', 'The existing line uses another transport.')\n if (options.phoneNumberId && line.providerNumberId !== options.phoneNumberId)\n throw new HostedAgentError('line_number_mismatch', 'The existing line is pinned to another WhatsApp number.')\n const retained = line.attachment?.status === 'active' ? line.attachment : undefined\n if (retained?.unknownSenders === 'onboard')\n throw new HostedAgentError('line_policy_migration_required', 'This line uses onboard admission. Manage it through Hub; the hosted-agent kit will not replace its policy.')\n const mode = options.mode ?? retained?.mode ?? 'shared'\n const declaredMembers = config.attachment?.members ?? (retained\n ? (await sandbox.lines.members(line.id).list())\n .filter(member => member.source === 'declared')\n .map(member => ({ address: member.address, role: member.role, label: member.label ?? undefined }))\n : members)\n const keyPrefix = line.attachment?.instance?.keyPrefix\n ?? (line.clientReference === 'hosted-agent' ? PERSON_KEY_PREFIX : `${PERSON_KEY_PREFIX}${line.id}:`)\n await sandbox.lines.attach({\n ...config.attachment,\n number: line.id,\n mode,\n members: declaredMembers,\n unknownSenders: config.attachment?.unknownSenders ?? retained?.unknownSenders ?? (mode === 'shared' && transport !== 'email' ? 'guest' : 'reject'),\n roles: config.attachment?.roles ?? retained?.roles ?? (mode === 'shared' ? { owner: PERSON, member: PERSON, guest: PERSON } : { owner: PERSON }),\n respond: { kind: 'agent', backend },\n limits: {\n ...retained?.limits,\n turnsPerMemberPerDay: config.freeTurnsPerDay ?? retained?.limits.turnsPerMemberPerDay ?? 20,\n ...config.attachment?.limits,\n },\n instance: config.attachment?.instance ?? retained?.instance ?? { keyPrefix, create },\n clientReference: config.attachment?.clientReference ?? retained?.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 return {\n /**\n * Create an address from an owned connection and attach it. Shared email\n * admits declared members only. Hub authenticates each mailbox by reply.\n * Other shared transports can admit isolated guests. Personal admits one\n * owner. Remove competing subscriptions before calling this method.\n */\n async attachLine(connectionId: string, options: HostedAgentLineOptions = {}): Promise<Line> {\n const transport = options.transport ?? 'imessage'\n validate(transport, options, true)\n // Hub deduplicates by provider identity. A new reference would conflict\n // with an existing WhatsApp line created by an earlier kit.\n const line = await sandbox.lines.fromConnection(transport === 'whatsapp'\n ? { connectionId, transport, phoneNumberId: options.phoneNumberId! }\n : { connectionId, transport })\n return attach(line, options)\n },\n /**\n * Attach an already-created owner-scoped line. Used during a host cutover\n * so the line id, connection, named-instance namespace and payer survive.\n * A 409 remains a 409. This method never detaches a competing attachment.\n */\n async attachExistingLine(lineId: string, options: HostedAgentLineOptions = {}): Promise<Line> {\n return attach(await sandbox.lines.get(lineId), options)\n },\n }\n}\n\nexport type HostedAgent = ReturnType<typeof createHostedAgent>\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED