Sign In

@absolutejs/agent-inbox

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@absolutejs/agent-inbox - npm Package Compare versions

Comparing version
0.1.1
to
0.2.0
+94
-2
dist/index.js

@@ -13,2 +13,10 @@ // @bun

listSubscriptions: async ({ tenantId, source, kind }) => [...subscriptions.values()].filter((item) => item.enabled && item.target.tenantId === tenantId && item.source === source && item.kinds.includes(kind)).map((item) => structuredClone(item)),
listSubscriptionInventory: async ({ tenantId, limit }) => [...subscriptions.values()].filter((item) => !tenantId || item.target.tenantId === tenantId).sort((left, right) => right.createdAt.localeCompare(left.createdAt)).slice(0, Math.max(1, Math.min(limit, 200))).map((item) => structuredClone(item)),
setSubscriptionEnabled: async ({ id, tenantId, enabled }) => {
const item = subscriptions.get(id);
if (!item || item.target.tenantId !== tenantId)
return false;
subscriptions.set(id, { ...item, enabled });
return true;
},
enqueue: async (value) => {

@@ -23,2 +31,10 @@ const key = `${value.subscriptionId}:${value.source}:${value.sourceEventId}`;

},
listMessages: async ({ tenantId, status, limit }) => [...messages.values()].filter((item) => (!tenantId || item.target.tenantId === tenantId) && (!status || item.status === status)).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).slice(0, Math.max(1, Math.min(limit, 200))).map((item) => structuredClone(item)),
cancelMessage: async ({ id, tenantId, now }) => {
const item = messages.get(id);
if (!item || item.target.tenantId !== tenantId || item.status !== "pending")
return false;
messages.set(id, { ...item, status: "cancelled", updatedAt: now });
return true;
},
claim: async ({ workerId, now, leaseExpiresAt }) => {

@@ -70,2 +86,10 @@ const row = [...messages.values()].filter((item) => (item.status === "pending" && Date.parse(item.notBefore) <= Date.parse(now) || item.status === "leased" && Date.parse(item.leaseExpiresAt ?? "") <= Date.parse(now)) && (!item.expiresAt || Date.parse(item.expiresAt) > Date.parse(now))).sort((a, b) => a.notBefore.localeCompare(b.notBefore))[0];

},
listSchedules: async ({ tenantId, limit }) => [...schedules.values()].filter((item) => !tenantId || item.target.tenantId === tenantId).sort((left, right) => right.createdAt.localeCompare(left.createdAt)).slice(0, Math.max(1, Math.min(limit, 200))).map((item) => structuredClone(item)),
setScheduleEnabled: async ({ id, tenantId, enabled }) => {
const item = schedules.get(id);
if (!item || item.target.tenantId !== tenantId)
return false;
schedules.set(id, { ...item, enabled });
return true;
},
claimDueSchedule: async ({ now }) => structuredClone([...schedules.values()].filter((item) => item.enabled && Date.parse(item.nextAt) <= Date.parse(now)).sort((a, b) => a.nextAt.localeCompare(b.nextAt))[0]),

@@ -109,2 +133,8 @@ advanceSchedule: async ({ id, expectedNextAt, nextAt }) => {

listSubscriptions: async (v) => (await client.query(`SELECT document FROM ${ns}.subscriptions WHERE tenant_id=$1 AND source=$2 AND enabled AND $3=ANY(kinds)`, [v.tenantId, v.source, v.kind])).rows.map((v2) => row(v2)),
listSubscriptionInventory: async (v) => {
const limit = Math.max(1, Math.min(v.limit, 200));
const result = v.tenantId ? await client.query(`SELECT document FROM ${ns}.subscriptions WHERE tenant_id=$1 ORDER BY document->>'createdAt' DESC LIMIT $2`, [v.tenantId, limit]) : await client.query(`SELECT document FROM ${ns}.subscriptions ORDER BY document->>'createdAt' DESC LIMIT $1`, [limit]);
return result.rows.map((value) => row(value));
},
setSubscriptionEnabled: async (v) => (await client.query(`UPDATE ${ns}.subscriptions SET enabled=$3,document=jsonb_set(document,'{enabled}',to_jsonb($3::boolean)) WHERE id=$1 AND tenant_id=$2 RETURNING id`, [v.id, v.tenantId, v.enabled])).rows.length === 1,
enqueue: async (v) => {

@@ -123,2 +153,22 @@ const result = await client.query(`INSERT INTO ${ns}.messages (id,subscription_id,source,source_event_id,status,not_before,expires_at,document) VALUES ($1,$2,$3,$4,$5,$6::timestamptz,$7::timestamptz,$8::jsonb) ON CONFLICT (subscription_id,source,source_event_id) DO UPDATE SET source_event_id=EXCLUDED.source_event_id RETURNING document`, [

},
listMessages: async (v) => {
const params = [];
const filters = [];
if (v.tenantId) {
params.push(v.tenantId);
filters.push(`document->'target'->>'tenantId'=$${params.length}`);
}
if (v.status) {
params.push(v.status);
filters.push(`status=$${params.length}`);
}
params.push(Math.max(1, Math.min(v.limit, 200)));
const where = filters.length ? ` WHERE ${filters.join(" AND ")}` : "";
return (await client.query(`SELECT document FROM ${ns}.messages${where} ORDER BY document->>'updatedAt' DESC LIMIT $${params.length}`, params)).rows.map((value) => row(value));
},
cancelMessage: async (v) => (await client.query(`UPDATE ${ns}.messages SET status='cancelled',document=document || $3::jsonb WHERE id=$1 AND document->'target'->>'tenantId'=$2 AND status='pending' RETURNING id`, [
v.id,
v.tenantId,
JSON.stringify({ status: "cancelled", updatedAt: v.now })
])).rows.length === 1,
claim: (v) => client.transaction(async (tx) => {

@@ -157,2 +207,8 @@ const found = row((await tx.query(`SELECT document FROM ${ns}.messages WHERE ((status='pending' AND not_before <= $1::timestamptz) OR (status='leased' AND lease_expires_at <= $1::timestamptz)) AND (expires_at IS NULL OR expires_at > $1::timestamptz) ORDER BY not_before FOR UPDATE SKIP LOCKED LIMIT 1`, [v.now])).rows[0]);

},
listSchedules: async (v) => {
const limit = Math.max(1, Math.min(v.limit, 200));
const result = v.tenantId ? await client.query(`SELECT document FROM ${ns}.schedules WHERE document->'target'->>'tenantId'=$1 ORDER BY document->>'createdAt' DESC LIMIT $2`, [v.tenantId, limit]) : await client.query(`SELECT document FROM ${ns}.schedules ORDER BY document->>'createdAt' DESC LIMIT $1`, [limit]);
return result.rows.map((value) => row(value));
},
setScheduleEnabled: async (v) => (await client.query(`UPDATE ${ns}.schedules SET enabled=$3,document=jsonb_set(document,'{enabled}',to_jsonb($3::boolean)) WHERE id=$1 AND document->'target'->>'tenantId'=$2 RETURNING id`, [v.id, v.tenantId, v.enabled])).rows.length === 1,
claimDueSchedule: async (v) => row((await client.query(`SELECT document FROM ${ns}.schedules WHERE enabled AND next_at <= $1::timestamptz ORDER BY next_at LIMIT 1`, [v.now])).rows[0]),

@@ -167,2 +223,18 @@ advanceSchedule: async (v) => (await client.query(`UPDATE ${ns}.schedules SET next_at=$3::timestamptz,document=jsonb_set(document,'{nextAt}',to_jsonb($3::text)) WHERE id=$1 AND next_at=$2::timestamptz RETURNING id`, [v.id, v.expectedNextAt, v.nextAt])).rows.length === 1

};
var MAX_MESSAGE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
var validateTarget = (target) => {
const { actions, wallTimeMs, ...nonnegative } = target.budget;
if (!Number.isSafeInteger(actions) || actions < 1)
throw new Error("Agent inbox action budget must be positive");
if (!Number.isSafeInteger(wallTimeMs) || wallTimeMs < 1)
throw new Error("Agent inbox wall-time budget must be positive");
if (Object.values(nonnegative).some((value) => !Number.isSafeInteger(value) || value < 0))
throw new Error("Agent inbox budgets must be nonnegative integers");
};
var validateDelivery = (value) => {
if (!Number.isSafeInteger(value.maxAttempts) || value.maxAttempts < 1 || value.maxAttempts > 10)
throw new Error("Agent inbox maxAttempts must be between 1 and 10");
if (!Number.isSafeInteger(value.messageTtlMs) || value.messageTtlMs < 1000 || value.messageTtlMs > MAX_MESSAGE_TTL_MS)
throw new Error("Agent inbox message TTL must be between one second and 30 days");
};

@@ -183,4 +255,14 @@ class AgentInboxVerificationError extends Error {

}) => ({
subscribe: (value) => store.saveSubscription(value),
subscribe: (value) => {
validateTarget(value.target);
validateDelivery(value);
if (value.kinds.length === 0)
throw new Error("Agent inbox subscription requires at least one kind");
return store.saveSubscription(value);
},
listSubscriptions: (tenantId, limit = 100) => store.listSubscriptionInventory({ tenantId, limit }),
setSubscriptionEnabled: (input) => store.setSubscriptionEnabled(input),
schedule: (value) => {
validateTarget(value.target);
validateDelivery(value);
if (!Number.isSafeInteger(value.intervalMs) || value.intervalMs < 1000)

@@ -190,2 +272,9 @@ throw new Error("Schedule interval must be at least one second");

},
listSchedules: (tenantId, limit = 100) => store.listSchedules({ tenantId, limit }),
setScheduleEnabled: (input) => store.setScheduleEnabled(input),
listMessages: (tenantId, status, limit = 100) => store.listMessages({ tenantId, status, limit }),
cancelMessage: (input) => store.cancelMessage({
...input,
now: new Date(now()).toISOString()
}),
ingest: async ({

@@ -239,4 +328,5 @@ source,

attempts: 0,
maxAttempts: 5,
maxAttempts: subscription.maxAttempts,
notBefore: timestamp,
expiresAt: new Date(now() + subscription.messageTtlMs).toISOString(),
createdAt: timestamp,

@@ -272,2 +362,3 @@ updatedAt: timestamp

maxAttempts: schedule.maxAttempts,
expiresAt: new Date(now() + schedule.messageTtlMs).toISOString(),
notBefore: occurrence,

@@ -332,2 +423,3 @@ createdAt: timestamp,

agent: message.target.agent,
budget: message.target.budget,
goal: message.target.goal,

@@ -334,0 +426,0 @@ input: {

@@ -14,3 +14,20 @@ import type { AgentInboxCodec, AgentInboxMessage, AgentInboxStore, AgentInboxSubscription, AgentInboxVerifier, AgentSchedule } from "./types";

subscribe: (value: AgentInboxSubscription) => Promise<void>;
listSubscriptions: (tenantId?: string, limit?: number) => Promise<AgentInboxSubscription[]>;
setSubscriptionEnabled: (input: {
id: string;
tenantId: string;
enabled: boolean;
}) => Promise<boolean>;
schedule: (value: AgentSchedule) => Promise<void>;
listSchedules: (tenantId?: string, limit?: number) => Promise<AgentSchedule[]>;
setScheduleEnabled: (input: {
id: string;
tenantId: string;
enabled: boolean;
}) => Promise<boolean>;
listMessages: (tenantId?: string, status?: AgentInboxMessage["status"], limit?: number) => Promise<AgentInboxMessage[]>;
cancelMessage: (input: {
id: string;
tenantId: string;
}) => Promise<boolean>;
ingest: ({ source, eventId, kind, body, headers, }: {

@@ -66,2 +83,3 @@ source: string;

idempotencyKey: string;
budget: AgentInboxMessage["target"]["budget"];
}): Promise<unknown>;

@@ -68,0 +86,0 @@ }) => ({ message, payload, }: {

@@ -6,2 +6,10 @@ export type AgentIdentityPin = {

};
export type AgentInboxBudget = {
actions: number;
costMicros: number;
inputTokens: number;
outputTokens: number;
spendMinor: number;
wallTimeMs: number;
};
export type AgentInboxTarget = {

@@ -12,2 +20,3 @@ tenantId: string;

agent: AgentIdentityPin;
budget: AgentInboxBudget;
goal: string;

@@ -20,2 +29,4 @@ };

kinds: string[];
maxAttempts: number;
messageTtlMs: number;
enabled: boolean;

@@ -37,3 +48,3 @@ createdAt: string;

};
status: "pending" | "leased" | "completed" | "dead_letter";
status: "pending" | "leased" | "completed" | "dead_letter" | "cancelled";
attempts: number;

@@ -59,2 +70,3 @@ maxAttempts: number;

maxAttempts: number;
messageTtlMs: number;
createdAt: string;

@@ -69,3 +81,22 @@ };

}): Promise<AgentInboxSubscription[]>;
listSubscriptionInventory(input: {
tenantId?: string;
limit: number;
}): Promise<AgentInboxSubscription[]>;
setSubscriptionEnabled(input: {
id: string;
tenantId: string;
enabled: boolean;
}): Promise<boolean>;
enqueue(value: AgentInboxMessage): Promise<AgentInboxMessage>;
listMessages(input: {
tenantId?: string;
status?: AgentInboxMessage["status"];
limit: number;
}): Promise<AgentInboxMessage[]>;
cancelMessage(input: {
id: string;
tenantId: string;
now: string;
}): Promise<boolean>;
claim(input: {

@@ -90,2 +121,11 @@ workerId: string;

saveSchedule(value: AgentSchedule): Promise<void>;
listSchedules(input: {
tenantId?: string;
limit: number;
}): Promise<AgentSchedule[]>;
setScheduleEnabled(input: {
id: string;
tenantId: string;
enabled: boolean;
}): Promise<boolean>;
claimDueSchedule(input: {

@@ -92,0 +132,0 @@ now: string;

+1
-1
{
"name": "@absolutejs/agent-inbox",
"version": "0.1.1",
"version": "0.2.0",
"description": "Durable verified webhooks, event subscriptions, schedules, leases, retries, and dead letters for AI agent triggers.",

@@ -5,0 +5,0 @@ "type": "module",

@@ -10,2 +10,7 @@ # @absolutejs/agent-inbox

Every subscription and schedule carries a complete runtime budget, bounded
delivery attempts, and a message TTL. Tenant-scoped inventories and explicit
subscription, schedule, and pending-message controls let owner and operator
interfaces manage retained triggers without querying the package schema.
Every message pins the target agent's signed discovery identity and preserves

@@ -12,0 +17,0 @@ verification provenance. The runtime adapter starts a durable