loopengine 0.1.66 → 0.1.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/dist/core/agent-config.d.ts +3 -0
- package/dist/core/approval-links.d.ts +16 -0
- package/dist/core/approval-links.js +76 -0
- package/dist/core/durable-approvals.d.ts +15 -0
- package/dist/core/durable-approvals.js +61 -3
- package/dist/core/http-notifier.d.ts +16 -1
- package/dist/core/http-notifier.js +49 -6
- package/dist/core/http-notify-triggers/slack.d.ts +21 -0
- package/dist/core/http-notify-triggers/slack.js +49 -10
- package/dist/core/http-notify-triggers/webhook.d.ts +19 -10
- package/dist/core/http-notify-triggers/webhook.js +84 -31
- package/dist/core/notifier-deliveries.d.ts +18 -0
- package/dist/core/notifier-deliveries.js +22 -0
- package/dist/core/notifier-file.d.ts +35 -0
- package/dist/core/notifier-file.js +104 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/web/agents-config-page.js +248 -2
- package/dist/web/console-shell.js +12 -4
- package/dist/web/decide-page.d.ts +16 -0
- package/dist/web/decide-page.js +138 -0
- package/dist/web/notifier-admin.d.ts +63 -0
- package/dist/web/notifier-admin.js +91 -0
- package/package.json +7 -1
|
@@ -39,56 +39,109 @@
|
|
|
39
39
|
// pending-approval/pending-question one hitting the same URL (see
|
|
40
40
|
// examples/notifier-handler/webhook.ts's own `verifyWebhookNotifier` for
|
|
41
41
|
// the receiving side of all three).
|
|
42
|
+
// Delivery is retried (RETRY_DELAYS_MS, a non-2xx answer counts as a
|
|
43
|
+
// failure), every attempt is recorded for the console's delivery log
|
|
44
|
+
// (core/notifier-deliveries.ts), and each payload carries where to send
|
|
45
|
+
// the decision back (`resolvePath`/`answerPath`, plus a full
|
|
46
|
+
// `resolveUrl`/`answerUrl` when LOOPENGINE_PUBLIC_URL says where this
|
|
47
|
+
// server is reachable) — all additive fields, so existing receivers keep
|
|
48
|
+
// working unchanged.
|
|
42
49
|
import { createHmac, randomUUID } from 'node:crypto';
|
|
50
|
+
import { recordDelivery } from '../notifier-deliveries.js';
|
|
51
|
+
import { decidePath } from '../approval-links.js';
|
|
52
|
+
/** Waits before each retry after the first attempt — four tries in all,
|
|
53
|
+
* spread over about 40 seconds. */
|
|
54
|
+
const RETRY_DELAYS_MS = [2_000, 10_000, 30_000];
|
|
55
|
+
function sign(body, secret) {
|
|
56
|
+
return `sha256=${createHmac('sha256', secret).update(body).digest('hex')}`;
|
|
57
|
+
}
|
|
58
|
+
/** Where this server is reachable: LOOPENGINE_PUBLIC_URL (e.g.
|
|
59
|
+
* https://acme.loopengine.app) when set, read per call so a change takes
|
|
60
|
+
* effect without a restart, else `fallback` (a notifier.yml's own
|
|
61
|
+
* publicUrl, remembered from the console). undefined when neither is
|
|
62
|
+
* known, in which case payloads carry only relative paths. */
|
|
63
|
+
export function publicBaseUrl(fallback) {
|
|
64
|
+
const base = process.env.LOOPENGINE_PUBLIC_URL || fallback;
|
|
65
|
+
return base ? base.replace(/\/+$/, '') : undefined;
|
|
66
|
+
}
|
|
67
|
+
function absolute(base, path) {
|
|
68
|
+
return base ? base + path : undefined;
|
|
69
|
+
}
|
|
70
|
+
async function attemptOnce(url, body, headers) {
|
|
71
|
+
try {
|
|
72
|
+
const res = await fetch(url, { method: 'POST', headers: { 'content-type': 'application/json', ...headers }, body });
|
|
73
|
+
return res.ok ? { ok: true, status: res.status } : { ok: false, status: res.status, error: `HTTP ${res.status}` };
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** Sends one signed POST, retrying on a network error or non-2xx answer,
|
|
80
|
+
* and records every attempt. Resolves with the last attempt's result —
|
|
81
|
+
* callers that must return right away (a pending decision is already
|
|
82
|
+
* recorded either way) don't await it. */
|
|
83
|
+
async function deliver(url, body, headers, meta, retryDelaysMs) {
|
|
84
|
+
let result = { ok: false };
|
|
85
|
+
for (let attempt = 1; attempt <= retryDelaysMs.length + 1; attempt++) {
|
|
86
|
+
if (attempt > 1)
|
|
87
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelaysMs[attempt - 2]));
|
|
88
|
+
result = await attemptOnce(url, body, headers);
|
|
89
|
+
recordDelivery({ at: new Date().toISOString(), url, attempt, ...meta, ...result });
|
|
90
|
+
if (result.ok)
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
console.error(`[loopengine] webhook delivery to ${url} failed after ${retryDelaysMs.length + 1} attempts (${meta.kind}${meta.pendingId ? ` ${meta.pendingId}` : ''}): ${result.error}`);
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
43
96
|
export class WebhookNotifier {
|
|
44
97
|
webhookUrl;
|
|
45
98
|
signingSecret;
|
|
99
|
+
retryDelaysMs;
|
|
100
|
+
publicUrl;
|
|
46
101
|
constructor(options) {
|
|
47
102
|
this.webhookUrl = options.webhookUrl;
|
|
48
103
|
this.signingSecret = options.signingSecret;
|
|
104
|
+
this.retryDelaysMs = options.retryDelaysMs ?? RETRY_DELAYS_MS;
|
|
105
|
+
this.publicUrl = options.publicUrl;
|
|
49
106
|
}
|
|
50
107
|
requestDurableApproval(tool, args, scope, reason) {
|
|
51
108
|
const pendingId = randomUUID();
|
|
52
109
|
const requestedAt = new Date().toISOString();
|
|
53
|
-
const
|
|
54
|
-
|
|
110
|
+
const resolvePath = `/pending-approvals/${pendingId}/resolve`;
|
|
111
|
+
const decide = decidePath(pendingId, 'approval');
|
|
112
|
+
const base = publicBaseUrl(this.publicUrl);
|
|
113
|
+
const body = JSON.stringify({
|
|
114
|
+
pendingId, tool, args, scope, reason, requestedAt,
|
|
115
|
+
kind: 'approval', agent: scope.agent, resolvePath, resolveUrl: absolute(base, resolvePath),
|
|
116
|
+
// A signed link a person can open to decide, no login needed.
|
|
117
|
+
decidePath: decide, decideUrl: absolute(base, decide),
|
|
118
|
+
});
|
|
119
|
+
void deliver(this.webhookUrl, body, { 'X-Actauth-Signature': sign(body, this.signingSecret) }, { agent: scope.agent, kind: 'approval', pendingId }, this.retryDelaysMs);
|
|
55
120
|
return { pendingId };
|
|
56
121
|
}
|
|
57
122
|
notifyPendingQuestion(question, options, agent, sessionId) {
|
|
58
123
|
const pendingId = randomUUID();
|
|
59
124
|
const requestedAt = new Date().toISOString();
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
* requestDurableApproval/notifyPendingQuestion return immediately, by
|
|
68
|
-
* contract (see DurableApprover/DurableQuestionHandler's own doc
|
|
69
|
-
* comments), so a delivery failure here has no synchronous way to
|
|
70
|
-
* surface to the caller — logged instead of thrown, since throwing
|
|
71
|
-
* would blow up a decision that has already, correctly, been recorded
|
|
72
|
-
* as pending. */
|
|
73
|
-
post(body, signatureHeader, pendingId) {
|
|
74
|
-
const signature = `sha256=${createHmac('sha256', this.signingSecret).update(body).digest('hex')}`;
|
|
75
|
-
fetch(this.webhookUrl, {
|
|
76
|
-
method: 'POST',
|
|
77
|
-
headers: { 'content-type': 'application/json', [signatureHeader]: signature },
|
|
78
|
-
body,
|
|
79
|
-
}).catch((err) => {
|
|
80
|
-
console.error(`[loopengine] WebhookNotifier: webhook delivery failed for pendingId '${pendingId}':`, err);
|
|
125
|
+
const answerPath = `/pending-questions/${pendingId}/answer`;
|
|
126
|
+
const decide = decidePath(pendingId, 'question');
|
|
127
|
+
const base = publicBaseUrl(this.publicUrl);
|
|
128
|
+
const body = JSON.stringify({
|
|
129
|
+
pendingId, question, options, agent, sessionId, requestedAt,
|
|
130
|
+
kind: 'question', answerPath, answerUrl: absolute(base, answerPath),
|
|
131
|
+
decidePath: decide, decideUrl: absolute(base, decide),
|
|
81
132
|
});
|
|
133
|
+
void deliver(this.webhookUrl, body, { 'X-Askuser-Signature': sign(body, this.signingSecret) }, { agent, kind: 'question', pendingId }, this.retryDelaysMs);
|
|
134
|
+
return { pendingId };
|
|
82
135
|
}
|
|
83
136
|
}
|
|
84
137
|
export function postLifecycleWebhook(webhookUrl, webhookSecret, event, context) {
|
|
85
138
|
const payload = JSON.stringify({ event, ...context, occurredAt: new Date().toISOString() });
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
});
|
|
139
|
+
void deliver(webhookUrl, payload, { 'X-Lifecycle-Signature': sign(payload, webhookSecret) }, { agent: String(context.agent ?? ''), kind: event }, RETRY_DELAYS_MS);
|
|
140
|
+
}
|
|
141
|
+
/** One signed `{event: "test"}` POST (lifecycle signature header), no
|
|
142
|
+
* retries, awaited — backs the console's "Send test" button so an
|
|
143
|
+
* operator sees right away whether the receiver accepts it. */
|
|
144
|
+
export function sendTestWebhook(webhookUrl, webhookSecret, agent) {
|
|
145
|
+
const payload = JSON.stringify({ event: 'test', agent, occurredAt: new Date().toISOString() });
|
|
146
|
+
return deliver(webhookUrl, payload, { 'X-Lifecycle-Signature': sign(payload, webhookSecret) }, { agent, kind: 'test' }, []);
|
|
94
147
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type NotifierDeliveryKind = 'approval' | 'question' | 'run_start' | 'run_finish' | 'test';
|
|
2
|
+
export interface NotifierDelivery {
|
|
3
|
+
at: string;
|
|
4
|
+
agent: string;
|
|
5
|
+
kind: NotifierDeliveryKind;
|
|
6
|
+
url: string;
|
|
7
|
+
pendingId?: string;
|
|
8
|
+
/** 1 for the first try, 2+ for retries. */
|
|
9
|
+
attempt: number;
|
|
10
|
+
ok: boolean;
|
|
11
|
+
/** The receiver's HTTP status, when it answered at all. */
|
|
12
|
+
status?: number;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function recordDelivery(delivery: NotifierDelivery): void;
|
|
16
|
+
/** Newest first. */
|
|
17
|
+
export declare function listDeliveries(agent: string): NotifierDelivery[];
|
|
18
|
+
export declare function clearDeliveries(): void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// A short, in-memory record of recent webhook deliveries per agent — what
|
|
2
|
+
// the console's Approvals section shows so an operator can see whether a
|
|
3
|
+
// notification actually reached the app on the other end (and why not).
|
|
4
|
+
// Deliberately not persisted: it's a debugging aid that resets with the
|
|
5
|
+
// process, not an audit log; the pending decision itself is what's
|
|
6
|
+
// durable (core/durable-approvals.ts).
|
|
7
|
+
const MAX_PER_AGENT = 50;
|
|
8
|
+
const byAgent = new Map();
|
|
9
|
+
export function recordDelivery(delivery) {
|
|
10
|
+
const list = byAgent.get(delivery.agent) ?? [];
|
|
11
|
+
list.unshift(delivery);
|
|
12
|
+
if (list.length > MAX_PER_AGENT)
|
|
13
|
+
list.length = MAX_PER_AGENT;
|
|
14
|
+
byAgent.set(delivery.agent, list);
|
|
15
|
+
}
|
|
16
|
+
/** Newest first. */
|
|
17
|
+
export function listDeliveries(agent) {
|
|
18
|
+
return [...(byAgent.get(agent) ?? [])];
|
|
19
|
+
}
|
|
20
|
+
export function clearDeliveries() {
|
|
21
|
+
byAgent.clear();
|
|
22
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { AgentEnv, HttpNotifierConfig, HttpNotifierEvent } from '#core/agent-config.js';
|
|
2
|
+
export type NotifierFileChannel = 'webhook' | 'slack' | 'lark';
|
|
3
|
+
export interface NotifierFile {
|
|
4
|
+
channel: NotifierFileChannel;
|
|
5
|
+
events: HttpNotifierEvent[];
|
|
6
|
+
/** webhook: where to POST. */
|
|
7
|
+
url?: string;
|
|
8
|
+
/** slack: channel to post into. */
|
|
9
|
+
channelId?: string;
|
|
10
|
+
/** lark: app id and chat to post into. */
|
|
11
|
+
appId?: string;
|
|
12
|
+
chatId?: string;
|
|
13
|
+
/** Where this server is reachable, for the links in each message —
|
|
14
|
+
* remembered from the console's own address when saving.
|
|
15
|
+
* LOOPENGINE_PUBLIC_URL, when set, wins over it. */
|
|
16
|
+
publicUrl?: string;
|
|
17
|
+
}
|
|
18
|
+
/** The env var each channel's secret is read from, per agent. */
|
|
19
|
+
export declare const NOTIFIER_SECRET_VARS: Record<NotifierFileChannel, string>;
|
|
20
|
+
export declare class NotifierFileError extends Error {
|
|
21
|
+
}
|
|
22
|
+
export declare function notifierFilePath(dir: string): string;
|
|
23
|
+
/** Throws NotifierFileError naming the first problem. */
|
|
24
|
+
export declare function validateNotifierFile(input: unknown): NotifierFile;
|
|
25
|
+
/** undefined when the agent has no notifier.yml. */
|
|
26
|
+
export declare function readNotifierFile(dir: string): NotifierFile | undefined;
|
|
27
|
+
export declare function writeNotifierFile(dir: string, input: unknown): NotifierFile;
|
|
28
|
+
export declare function removeNotifierFile(dir: string): void;
|
|
29
|
+
/** The HttpNotifierConfig a notifier.yml describes, with its secret read
|
|
30
|
+
* from `env` — or which secret is missing, so nothing is sent unsigned
|
|
31
|
+
* or unauthenticated. */
|
|
32
|
+
export declare function notifierConfigFromFile(file: NotifierFile, env: AgentEnv): {
|
|
33
|
+
config?: HttpNotifierConfig;
|
|
34
|
+
missingSecret?: string;
|
|
35
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// agents/<name>/notifier.yml — where an agent's "ask" approvals and
|
|
2
|
+
// questions go when nobody is watching a live chat, set from the console
|
|
3
|
+
// (Permissions → "Where approval requests and questions are sent") instead of in code. Only consulted when the
|
|
4
|
+
// agent's own AgentConfig.httpNotifier is unset; code always wins.
|
|
5
|
+
//
|
|
6
|
+
// The file holds only non-secret settings. The one secret each channel
|
|
7
|
+
// needs (a webhook signing secret, a Slack bot token, a Lark app secret)
|
|
8
|
+
// lives in the agent's own env (agents/<name>/.env by default, through
|
|
9
|
+
// core/secret-store.ts) under a fixed name, so it's never written into a
|
|
10
|
+
// file that might get committed.
|
|
11
|
+
import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
14
|
+
/** The env var each channel's secret is read from, per agent. */
|
|
15
|
+
export const NOTIFIER_SECRET_VARS = {
|
|
16
|
+
webhook: 'LOOPENGINE_NOTIFIER_WEBHOOK_SECRET',
|
|
17
|
+
slack: 'LOOPENGINE_NOTIFIER_SLACK_BOT_TOKEN',
|
|
18
|
+
lark: 'LOOPENGINE_NOTIFIER_LARK_APP_SECRET',
|
|
19
|
+
};
|
|
20
|
+
const CHANNELS = ['webhook', 'slack', 'lark'];
|
|
21
|
+
const EVENTS = ['approval', 'question', 'run_start', 'run_finish'];
|
|
22
|
+
export class NotifierFileError extends Error {
|
|
23
|
+
}
|
|
24
|
+
export function notifierFilePath(dir) {
|
|
25
|
+
return join(dir, 'notifier.yml');
|
|
26
|
+
}
|
|
27
|
+
/** Throws NotifierFileError naming the first problem. */
|
|
28
|
+
export function validateNotifierFile(input) {
|
|
29
|
+
if (!input || typeof input !== 'object')
|
|
30
|
+
throw new NotifierFileError('notifier settings must be an object');
|
|
31
|
+
const raw = input;
|
|
32
|
+
const channel = raw.channel;
|
|
33
|
+
if (!CHANNELS.includes(channel))
|
|
34
|
+
throw new NotifierFileError(`channel must be one of: ${CHANNELS.join(', ')}`);
|
|
35
|
+
const events = Array.isArray(raw.events) ? raw.events : [];
|
|
36
|
+
if (!events.length || events.some((e) => !EVENTS.includes(e))) {
|
|
37
|
+
throw new NotifierFileError(`events must be a non-empty list of: ${EVENTS.join(', ')}`);
|
|
38
|
+
}
|
|
39
|
+
const str = (key) => (typeof raw[key] === 'string' && raw[key].trim() ? raw[key].trim() : undefined);
|
|
40
|
+
const file = { channel, events: events };
|
|
41
|
+
const publicUrl = str('publicUrl');
|
|
42
|
+
if (publicUrl) {
|
|
43
|
+
if (!/^https?:\/\/[^/]+/.test(publicUrl))
|
|
44
|
+
throw new NotifierFileError('publicUrl must be an http(s) URL');
|
|
45
|
+
file.publicUrl = publicUrl.replace(/\/+$/, '');
|
|
46
|
+
}
|
|
47
|
+
if (channel === 'webhook') {
|
|
48
|
+
const url = str('url');
|
|
49
|
+
if (!url || !/^https?:\/\//.test(url))
|
|
50
|
+
throw new NotifierFileError('url must be an http(s) URL');
|
|
51
|
+
file.url = url;
|
|
52
|
+
}
|
|
53
|
+
else if (channel === 'slack') {
|
|
54
|
+
const channelId = str('channelId');
|
|
55
|
+
if (!channelId)
|
|
56
|
+
throw new NotifierFileError('channelId is required for slack');
|
|
57
|
+
file.channelId = channelId;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const appId = str('appId');
|
|
61
|
+
const chatId = str('chatId');
|
|
62
|
+
if (!appId || !chatId)
|
|
63
|
+
throw new NotifierFileError('appId and chatId are required for lark');
|
|
64
|
+
file.appId = appId;
|
|
65
|
+
file.chatId = chatId;
|
|
66
|
+
}
|
|
67
|
+
return file;
|
|
68
|
+
}
|
|
69
|
+
/** undefined when the agent has no notifier.yml. */
|
|
70
|
+
export function readNotifierFile(dir) {
|
|
71
|
+
const path = notifierFilePath(dir);
|
|
72
|
+
if (!existsSync(path))
|
|
73
|
+
return undefined;
|
|
74
|
+
return validateNotifierFile(parseYaml(readFileSync(path, 'utf8')));
|
|
75
|
+
}
|
|
76
|
+
export function writeNotifierFile(dir, input) {
|
|
77
|
+
const file = validateNotifierFile(input);
|
|
78
|
+
const header = '# Where this agent sends approval requests and questions when it is called over HTTP.\n' +
|
|
79
|
+
`# Edited from the console (Permissions tab). The secret lives in this agent's env as ${NOTIFIER_SECRET_VARS[file.channel]}.\n`;
|
|
80
|
+
writeFileSync(notifierFilePath(dir), header + stringifyYaml(file));
|
|
81
|
+
return file;
|
|
82
|
+
}
|
|
83
|
+
export function removeNotifierFile(dir) {
|
|
84
|
+
rmSync(notifierFilePath(dir), { force: true });
|
|
85
|
+
}
|
|
86
|
+
/** The HttpNotifierConfig a notifier.yml describes, with its secret read
|
|
87
|
+
* from `env` — or which secret is missing, so nothing is sent unsigned
|
|
88
|
+
* or unauthenticated. */
|
|
89
|
+
export function notifierConfigFromFile(file, env) {
|
|
90
|
+
const secretVar = NOTIFIER_SECRET_VARS[file.channel];
|
|
91
|
+
const secret = env.get(secretVar);
|
|
92
|
+
if (!secret)
|
|
93
|
+
return { missingSecret: secretVar };
|
|
94
|
+
switch (file.channel) {
|
|
95
|
+
case 'webhook':
|
|
96
|
+
return { config: { channel: 'webhook', config: { webhookUrl: file.url, webhookSecret: secret, publicUrl: file.publicUrl }, events: file.events } };
|
|
97
|
+
case 'slack':
|
|
98
|
+
// No interactive buttons: a console-configured Slack notifier has
|
|
99
|
+
// no interactivity endpoint behind it; the decision link replaces them.
|
|
100
|
+
return { config: { channel: 'slack', config: { botToken: secret, channelId: file.channelId, publicUrl: file.publicUrl, buttons: false }, events: file.events } };
|
|
101
|
+
case 'lark':
|
|
102
|
+
return { config: { channel: 'lark', config: { appId: file.appId, appSecret: secret, chatId: file.chatId }, events: file.events } };
|
|
103
|
+
}
|
|
104
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,13 @@ export { createAgentEnv, agentDirFor, envScopeOf, type EnvScope } from '#core/ag
|
|
|
7
7
|
export { FileSecretStore, setSecretStore, getSecretStore, type SecretStore } from '#core/secret-store.js';
|
|
8
8
|
export { signProxyIdentity, verifyProxyIdentity, TRUSTED_PROXY_IDENTITY_HEADER, type ProxyIdentity } from '#core/trusted-proxy.js';
|
|
9
9
|
export { setUsageSink, recordUsage, type UsageEvent, type UsageSink } from '#core/usage.js';
|
|
10
|
+
export { readNotifierFile, writeNotifierFile, removeNotifierFile, validateNotifierFile, notifierConfigFromFile, NotifierFileError, NOTIFIER_SECRET_VARS, type NotifierFile } from '#core/notifier-file.js';
|
|
11
|
+
export { listDeliveries, recordDelivery, type NotifierDelivery } from '#core/notifier-deliveries.js';
|
|
12
|
+
export { describeNotifier } from '#core/http-notifier.js';
|
|
13
|
+
export { createDecisionToken, verifyDecisionToken, decidePath, decideLinkMaker, decisionLinkTtlMs, type DecisionKind } from '#core/approval-links.js';
|
|
14
|
+
export { renderDecidePage, renderDecideGonePage, renderDecideInvalidPage, type DecidePageItem } from './web/decide-page.js';
|
|
15
|
+
export { sendTestWebhook } from '#core/http-notify-triggers/webhook.js';
|
|
16
|
+
export { describeAgentNotifier, saveAgentNotifier, removeAgentNotifier, testAgentNotifier, listAgentPending, type DefaultWebhook } from './web/notifier-admin.js';
|
|
10
17
|
export type * from './core/loop-events.js';
|
|
11
18
|
export { streamMessage, streamMessageWithCallbacks, sendMessage, approveCall, denyCall, answerQuestion, getSessionHistory, } from './core/client.js';
|
|
12
19
|
export type { RequestOptions, SendMessageResult, PendingApprovalResult, PendingQuestionResult, PendingResult, MessageResult, StreamMessageCallbacks, } from './core/client.js';
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,13 @@ export { createAgentEnv, agentDirFor, envScopeOf } from '#core/agent-env.js';
|
|
|
9
9
|
export { FileSecretStore, setSecretStore, getSecretStore } from '#core/secret-store.js';
|
|
10
10
|
export { signProxyIdentity, verifyProxyIdentity, TRUSTED_PROXY_IDENTITY_HEADER } from '#core/trusted-proxy.js';
|
|
11
11
|
export { setUsageSink, recordUsage } from '#core/usage.js';
|
|
12
|
+
export { readNotifierFile, writeNotifierFile, removeNotifierFile, validateNotifierFile, notifierConfigFromFile, NotifierFileError, NOTIFIER_SECRET_VARS } from '#core/notifier-file.js';
|
|
13
|
+
export { listDeliveries, recordDelivery } from '#core/notifier-deliveries.js';
|
|
14
|
+
export { describeNotifier } from '#core/http-notifier.js';
|
|
15
|
+
export { createDecisionToken, verifyDecisionToken, decidePath, decideLinkMaker, decisionLinkTtlMs } from '#core/approval-links.js';
|
|
16
|
+
export { renderDecidePage, renderDecideGonePage, renderDecideInvalidPage } from './web/decide-page.js';
|
|
17
|
+
export { sendTestWebhook } from '#core/http-notify-triggers/webhook.js';
|
|
18
|
+
export { describeAgentNotifier, saveAgentNotifier, removeAgentNotifier, testAgentNotifier, listAgentPending } from './web/notifier-admin.js';
|
|
12
19
|
// The framework-agnostic browser client for adapters/http.ts's own wire
|
|
13
20
|
// protocol (see client.ts's own header comment) — this, not a deep
|
|
14
21
|
// subpath import, is how a consumer's React/Vue/etc. chat UI is meant to
|