@msg91comm/m91 0.0.0-stage → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +94 -0
- package/LICENSE +21 -0
- package/README.md +647 -2
- package/dist/src/client.d.ts +165 -0
- package/dist/src/client.d.ts.map +1 -0
- package/dist/src/client.js +221 -0
- package/dist/src/client.js.map +1 -0
- package/dist/src/exceptions.d.ts +114 -0
- package/dist/src/exceptions.d.ts.map +1 -0
- package/dist/src/exceptions.js +199 -0
- package/dist/src/exceptions.js.map +1 -0
- package/dist/src/http.d.ts +71 -0
- package/dist/src/http.d.ts.map +1 -0
- package/dist/src/http.js +150 -0
- package/dist/src/http.js.map +1 -0
- package/dist/src/index.d.ts +49 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +45 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/integrations/claude-agent-sdk.d.ts +63 -0
- package/dist/src/integrations/claude-agent-sdk.d.ts.map +1 -0
- package/dist/src/integrations/claude-agent-sdk.js +109 -0
- package/dist/src/integrations/claude-agent-sdk.js.map +1 -0
- package/dist/src/integrations/google-adk.d.ts +47 -0
- package/dist/src/integrations/google-adk.d.ts.map +1 -0
- package/dist/src/integrations/google-adk.js +84 -0
- package/dist/src/integrations/google-adk.js.map +1 -0
- package/dist/src/integrations/index.d.ts +26 -0
- package/dist/src/integrations/index.d.ts.map +1 -0
- package/dist/src/integrations/index.js +25 -0
- package/dist/src/integrations/index.js.map +1 -0
- package/dist/src/integrations/langchain.d.ts +94 -0
- package/dist/src/integrations/langchain.d.ts.map +1 -0
- package/dist/src/integrations/langchain.js +129 -0
- package/dist/src/integrations/langchain.js.map +1 -0
- package/dist/src/integrations/langgraph.d.ts +79 -0
- package/dist/src/integrations/langgraph.d.ts.map +1 -0
- package/dist/src/integrations/langgraph.js +114 -0
- package/dist/src/integrations/langgraph.js.map +1 -0
- package/dist/src/integrations/mastra.d.ts +75 -0
- package/dist/src/integrations/mastra.d.ts.map +1 -0
- package/dist/src/integrations/mastra.js +109 -0
- package/dist/src/integrations/mastra.js.map +1 -0
- package/dist/src/integrations/openai-agents.d.ts +61 -0
- package/dist/src/integrations/openai-agents.d.ts.map +1 -0
- package/dist/src/integrations/openai-agents.js +127 -0
- package/dist/src/integrations/openai-agents.js.map +1 -0
- package/dist/src/integrations/specs.d.ts +173 -0
- package/dist/src/integrations/specs.d.ts.map +1 -0
- package/dist/src/integrations/specs.js +217 -0
- package/dist/src/integrations/specs.js.map +1 -0
- package/dist/src/integrations/vercel-ai.d.ts +74 -0
- package/dist/src/integrations/vercel-ai.d.ts.map +1 -0
- package/dist/src/integrations/vercel-ai.js +102 -0
- package/dist/src/integrations/vercel-ai.js.map +1 -0
- package/dist/src/integrations/zod-shapes.d.ts +19 -0
- package/dist/src/integrations/zod-shapes.d.ts.map +1 -0
- package/dist/src/integrations/zod-shapes.js +44 -0
- package/dist/src/integrations/zod-shapes.js.map +1 -0
- package/dist/src/models.d.ts +274 -0
- package/dist/src/models.d.ts.map +1 -0
- package/dist/src/models.js +383 -0
- package/dist/src/models.js.map +1 -0
- package/dist/src/platform.d.ts +176 -0
- package/dist/src/platform.d.ts.map +1 -0
- package/dist/src/platform.js +286 -0
- package/dist/src/platform.js.map +1 -0
- package/dist/src/url-builder.d.ts +65 -0
- package/dist/src/url-builder.d.ts.map +1 -0
- package/dist/src/url-builder.js +167 -0
- package/dist/src/url-builder.js.map +1 -0
- package/examples/approval-gate.ts +35 -0
- package/examples/basic-alert.ts +53 -0
- package/examples/claude-agent-sdk.ts +23 -0
- package/examples/deep-agents.ts +31 -0
- package/examples/google-adk.ts +39 -0
- package/examples/langchain-agent.ts +65 -0
- package/examples/langgraph-hitl.ts +70 -0
- package/examples/mastra.ts +41 -0
- package/examples/openai-agents.ts +33 -0
- package/examples/vercel-ai-agent.ts +34 -0
- package/examples/webhook-urls.ts +44 -0
- package/package.json +135 -4
- package/src/client.ts +324 -0
- package/src/exceptions.ts +229 -0
- package/src/http.ts +174 -0
- package/src/index.ts +91 -0
- package/src/integrations/claude-agent-sdk.ts +158 -0
- package/src/integrations/google-adk.ts +122 -0
- package/src/integrations/index.ts +27 -0
- package/src/integrations/langchain.ts +207 -0
- package/src/integrations/langgraph.ts +142 -0
- package/src/integrations/mastra.ts +178 -0
- package/src/integrations/openai-agents.ts +173 -0
- package/src/integrations/specs.ts +328 -0
- package/src/integrations/vercel-ai.ts +151 -0
- package/src/integrations/zod-shapes.ts +55 -0
- package/src/models.ts +660 -0
- package/src/platform.ts +343 -0
- package/src/url-builder.ts +225 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The send-link client: raise an alert, read what was decided, close it.
|
|
3
|
+
*
|
|
4
|
+
* A send link is a credential in a URL. It goes in a Grafana webhook field, a
|
|
5
|
+
* cron, a CI job or an agent's environment, and it needs no header, no account
|
|
6
|
+
* and no library — which is why this client is a convenience over one URL
|
|
7
|
+
* rather than the only way in.
|
|
8
|
+
*
|
|
9
|
+
* Two things it does that a bare `fetch` does not, and both are why it exists:
|
|
10
|
+
*
|
|
11
|
+
* - **It checks that somebody was actually reached.** A 201 means the alert was
|
|
12
|
+
* filed, not that a phone rang.
|
|
13
|
+
* - **It reads the decision, not the responses.** Only a SHARED response decides.
|
|
14
|
+
*/
|
|
15
|
+
import { type TransportOptions } from './http.js';
|
|
16
|
+
import { type AlertDetail, type AlertResponse, type ChannelInfo, type Decision, type ResponseOption, type Severity } from './models.js';
|
|
17
|
+
/**
|
|
18
|
+
* Below this, polling is a busy loop against a rate-limited endpoint and buys
|
|
19
|
+
* nothing: a human waking up and reaching for a phone is not a sub-second event.
|
|
20
|
+
*/
|
|
21
|
+
export declare const MIN_POLL_INTERVAL_MS = 2000;
|
|
22
|
+
export interface SendOptions {
|
|
23
|
+
description?: string;
|
|
24
|
+
/**
|
|
25
|
+
* LOW and MEDIUM deliver at alarm volume through Do Not Disturb. **HIGH and
|
|
26
|
+
* CRITICAL also place a voice call** if nobody answers in 45 seconds, so they
|
|
27
|
+
* are never the default — "I didn't specify" must not mean "ring everyone".
|
|
28
|
+
*/
|
|
29
|
+
severity?: Severity;
|
|
30
|
+
/**
|
|
31
|
+
* Your own stable id for *the condition*, not for this occurrence. The same
|
|
32
|
+
* disk filling up is the same `customId` every time. Built from a timestamp
|
|
33
|
+
* or a UUID it defeats deduplication — a check running every minute raises
|
|
34
|
+
* sixty alerts an hour — and leaves nothing stable to close by.
|
|
35
|
+
*/
|
|
36
|
+
customId?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Left false, the alert stays open until somebody closes it, even after a
|
|
39
|
+
* SHARED response claims it. Set true when the first person taking it on is
|
|
40
|
+
* genuinely the end of the matter.
|
|
41
|
+
*/
|
|
42
|
+
isAutoClose?: boolean;
|
|
43
|
+
responseOptions?: Array<ResponseOption | string>;
|
|
44
|
+
/**
|
|
45
|
+
* Whether the free-text "Something else" reply is offered. Tri-state: leave
|
|
46
|
+
* it unset and the server's default (on) stands.
|
|
47
|
+
*/
|
|
48
|
+
other?: boolean;
|
|
49
|
+
/** https links to files the alert is about. URLs, never bytes. */
|
|
50
|
+
attachments?: string[];
|
|
51
|
+
/** Where the outcome is POSTed when somebody answers. */
|
|
52
|
+
callbackUrl?: string;
|
|
53
|
+
}
|
|
54
|
+
export interface WaitOptions {
|
|
55
|
+
timeoutMs?: number;
|
|
56
|
+
pollIntervalMs?: number;
|
|
57
|
+
throwOnTimeout?: boolean;
|
|
58
|
+
}
|
|
59
|
+
export interface ApprovalOptions {
|
|
60
|
+
customId: string;
|
|
61
|
+
description?: string;
|
|
62
|
+
severity?: Severity;
|
|
63
|
+
approveLabel?: string;
|
|
64
|
+
rejectLabel?: string;
|
|
65
|
+
timeoutMs?: number;
|
|
66
|
+
pollIntervalMs?: number;
|
|
67
|
+
/** A timeout returns false. There is deliberately no option making it true. */
|
|
68
|
+
timeoutMeansReject?: boolean;
|
|
69
|
+
}
|
|
70
|
+
export interface ClientOptions extends TransportOptions {
|
|
71
|
+
/**
|
|
72
|
+
* Throw `M91DeliveryError` when a send reaches nobody. On by default, because
|
|
73
|
+
* the silent version of this failure is an alert nobody ever hears about.
|
|
74
|
+
*/
|
|
75
|
+
throwOnZeroDelivery?: boolean;
|
|
76
|
+
}
|
|
77
|
+
/** Alerts one channel, through one send link. */
|
|
78
|
+
export declare class M91Client {
|
|
79
|
+
readonly sendLink: string;
|
|
80
|
+
readonly throwOnZeroDelivery: boolean;
|
|
81
|
+
private readonly http;
|
|
82
|
+
private readonly sleep;
|
|
83
|
+
private readonly now;
|
|
84
|
+
constructor(sendLink: string, options?: ClientOptions);
|
|
85
|
+
/** Redacted, always: a token in a log, a trace or a crash report can wake a team. */
|
|
86
|
+
toString(): string;
|
|
87
|
+
private url;
|
|
88
|
+
/**
|
|
89
|
+
* Check the link works, and see which channel it points at.
|
|
90
|
+
*
|
|
91
|
+
* **Raises no alert.** Safe on startup, in a health check, or while somebody
|
|
92
|
+
* is pasting the URL into a settings page — which is the point: a mistyped
|
|
93
|
+
* link otherwise shows "connected" and only reveals itself days later, when
|
|
94
|
+
* an alert silently fails to fire.
|
|
95
|
+
*/
|
|
96
|
+
verify(): Promise<ChannelInfo>;
|
|
97
|
+
/** Raise an alert on this channel. */
|
|
98
|
+
send(title: string, options?: SendOptions): Promise<AlertResponse>;
|
|
99
|
+
/**
|
|
100
|
+
* Read one alert back. `null` when there is no such alert on this channel.
|
|
101
|
+
*
|
|
102
|
+
* The id is **either** the `customId` you invented or the `_id` the send
|
|
103
|
+
* returned — a caller knows one of them and should not have to say which.
|
|
104
|
+
*/
|
|
105
|
+
getAlert(customIdOrAlertId: string): Promise<AlertDetail | null>;
|
|
106
|
+
/**
|
|
107
|
+
* Wait until somebody takes the alert on, or the clock runs out.
|
|
108
|
+
*
|
|
109
|
+
* Resolves with the SHARED response that claimed it. **PERSONAL responses
|
|
110
|
+
* never end this wait** — a "Seen" is an acknowledgement, not an answer, and
|
|
111
|
+
* treating it as one is the mistake `decision` exists to prevent.
|
|
112
|
+
*
|
|
113
|
+
* Stops early and resolves `null` if the alert is closed without ever being
|
|
114
|
+
* claimed: there is nothing left to wait for.
|
|
115
|
+
*/
|
|
116
|
+
waitForDecision(customId: string, options?: WaitOptions): Promise<Decision | null>;
|
|
117
|
+
/**
|
|
118
|
+
* Close an alert this link raised. Always `true` when there is nothing open.
|
|
119
|
+
*
|
|
120
|
+
* A monitor that sees the problem heal closes it itself rather than waiting
|
|
121
|
+
* for a person to wake up and notice.
|
|
122
|
+
*
|
|
123
|
+
* **404 and 403 are success.** "No such alert" and "not yours to close" both
|
|
124
|
+
* mean the alert is not open on this channel, which is the state the caller
|
|
125
|
+
* asked for. Anything else would make every close a branch, and every retry
|
|
126
|
+
* after a dropped response a false failure.
|
|
127
|
+
*/
|
|
128
|
+
close(options: {
|
|
129
|
+
customId?: string;
|
|
130
|
+
alertId?: string;
|
|
131
|
+
}): Promise<boolean>;
|
|
132
|
+
/**
|
|
133
|
+
* Ask a human, on their phone, and wait for the answer. `true` = approved.
|
|
134
|
+
*
|
|
135
|
+
* The shape of every human-in-the-loop gate: raise the question, block, act
|
|
136
|
+
* on the answer, close the alert either way. Severity defaults to HIGH here —
|
|
137
|
+
* unlike `send` — because something is *waiting* on the answer, so a phone
|
|
138
|
+
* call after 45 seconds of silence is the point rather than an escalation.
|
|
139
|
+
*
|
|
140
|
+
* Both labels are SHARED: approving and rejecting are each a final answer
|
|
141
|
+
* that settles the question for everybody, and neither is an "I saw it".
|
|
142
|
+
*
|
|
143
|
+
* On timeout with `timeoutMeansReject` (the default) it resolves false —
|
|
144
|
+
* nobody answered, so nothing consequential happens. Set it false to get
|
|
145
|
+
* `M91TimeoutError` instead. There is deliberately no way to make a timeout
|
|
146
|
+
* mean approved.
|
|
147
|
+
*/
|
|
148
|
+
requestApproval(title: string, options: ApprovalOptions): Promise<boolean>;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* A 2xx that woke nobody is a failure, and only the caller can decide what to do.
|
|
152
|
+
*
|
|
153
|
+
* `delivered` counts phones actually sent to, so this fires when a channel's
|
|
154
|
+
* devices are all stale or its members have not installed the app — a case that
|
|
155
|
+
* answers 201 and looks exactly like success.
|
|
156
|
+
*/
|
|
157
|
+
export declare function checkDelivery(alert: AlertResponse, enabled: boolean): void;
|
|
158
|
+
/**
|
|
159
|
+
* Whether one poll ends the wait. Pure, so the rule lives in one place.
|
|
160
|
+
*/
|
|
161
|
+
export declare function waitVerdict(detail: AlertDetail | null): {
|
|
162
|
+
stop: boolean;
|
|
163
|
+
decision: Decision | null;
|
|
164
|
+
};
|
|
165
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The send-link client: raise an alert, read what was decided, close it.
|
|
3
|
+
*
|
|
4
|
+
* A send link is a credential in a URL. It goes in a Grafana webhook field, a
|
|
5
|
+
* cron, a CI job or an agent's environment, and it needs no header, no account
|
|
6
|
+
* and no library — which is why this client is a convenience over one URL
|
|
7
|
+
* rather than the only way in.
|
|
8
|
+
*
|
|
9
|
+
* Two things it does that a bare `fetch` does not, and both are why it exists:
|
|
10
|
+
*
|
|
11
|
+
* - **It checks that somebody was actually reached.** A 201 means the alert was
|
|
12
|
+
* filed, not that a phone rang.
|
|
13
|
+
* - **It reads the decision, not the responses.** Only a SHARED response decides.
|
|
14
|
+
*/
|
|
15
|
+
import { M91DeliveryError, M91ForbiddenError, M91NotFoundError, M91TimeoutError, M91ValidationError, } from './exceptions.js';
|
|
16
|
+
import { Transport, redact } from './http.js';
|
|
17
|
+
import { buildAlertPayload, isApproved, parseAlertDetail, parseAlertResponse, parseChannelInfo, } from './models.js';
|
|
18
|
+
import { normalizeSendLink } from './url-builder.js';
|
|
19
|
+
/**
|
|
20
|
+
* Below this, polling is a busy loop against a rate-limited endpoint and buys
|
|
21
|
+
* nothing: a human waking up and reaching for a phone is not a sub-second event.
|
|
22
|
+
*/
|
|
23
|
+
export const MIN_POLL_INTERVAL_MS = 2000;
|
|
24
|
+
/** Alerts one channel, through one send link. */
|
|
25
|
+
export class M91Client {
|
|
26
|
+
sendLink;
|
|
27
|
+
throwOnZeroDelivery;
|
|
28
|
+
http;
|
|
29
|
+
sleep;
|
|
30
|
+
now;
|
|
31
|
+
constructor(sendLink, options = {}) {
|
|
32
|
+
this.sendLink = normalizeSendLink(sendLink);
|
|
33
|
+
this.throwOnZeroDelivery = options.throwOnZeroDelivery !== false;
|
|
34
|
+
this.http = new Transport(options);
|
|
35
|
+
this.sleep = options.sleepImpl ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
36
|
+
this.now = () => Date.now();
|
|
37
|
+
}
|
|
38
|
+
/** Redacted, always: a token in a log, a trace or a crash report can wake a team. */
|
|
39
|
+
toString() {
|
|
40
|
+
return `M91Client(${redact(this.sendLink)})`;
|
|
41
|
+
}
|
|
42
|
+
url(suffix = '') {
|
|
43
|
+
return this.sendLink + suffix;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Check the link works, and see which channel it points at.
|
|
47
|
+
*
|
|
48
|
+
* **Raises no alert.** Safe on startup, in a health check, or while somebody
|
|
49
|
+
* is pasting the URL into a settings page — which is the point: a mistyped
|
|
50
|
+
* link otherwise shows "connected" and only reveals itself days later, when
|
|
51
|
+
* an alert silently fails to fire.
|
|
52
|
+
*/
|
|
53
|
+
async verify() {
|
|
54
|
+
return parseChannelInfo(await this.http.request({ method: 'GET', url: this.url() }));
|
|
55
|
+
}
|
|
56
|
+
/** Raise an alert on this channel. */
|
|
57
|
+
async send(title, options = {}) {
|
|
58
|
+
const payload = buildAlertPayload(title, options);
|
|
59
|
+
const alert = parseAlertResponse(await this.http.request({ method: 'POST', url: this.url(), body: payload }));
|
|
60
|
+
checkDelivery(alert, this.throwOnZeroDelivery);
|
|
61
|
+
return alert;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Read one alert back. `null` when there is no such alert on this channel.
|
|
65
|
+
*
|
|
66
|
+
* The id is **either** the `customId` you invented or the `_id` the send
|
|
67
|
+
* returned — a caller knows one of them and should not have to say which.
|
|
68
|
+
*/
|
|
69
|
+
async getAlert(customIdOrAlertId) {
|
|
70
|
+
try {
|
|
71
|
+
const raw = await this.http.request({
|
|
72
|
+
method: 'GET',
|
|
73
|
+
url: this.url(`/alerts/${encodeURIComponent(String(customIdOrAlertId))}`),
|
|
74
|
+
});
|
|
75
|
+
return parseAlertDetail(raw);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (error instanceof M91NotFoundError)
|
|
79
|
+
return null;
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Wait until somebody takes the alert on, or the clock runs out.
|
|
85
|
+
*
|
|
86
|
+
* Resolves with the SHARED response that claimed it. **PERSONAL responses
|
|
87
|
+
* never end this wait** — a "Seen" is an acknowledgement, not an answer, and
|
|
88
|
+
* treating it as one is the mistake `decision` exists to prevent.
|
|
89
|
+
*
|
|
90
|
+
* Stops early and resolves `null` if the alert is closed without ever being
|
|
91
|
+
* claimed: there is nothing left to wait for.
|
|
92
|
+
*/
|
|
93
|
+
async waitForDecision(customId, options = {}) {
|
|
94
|
+
const timeoutMs = options.timeoutMs ?? 300_000;
|
|
95
|
+
const requested = options.pollIntervalMs ?? 5000;
|
|
96
|
+
if (requested < MIN_POLL_INTERVAL_MS) {
|
|
97
|
+
console.warn(`[m91] pollIntervalMs=${requested} is below ${MIN_POLL_INTERVAL_MS}. The send link is ` +
|
|
98
|
+
`rate limited to 60 requests a minute and a person cannot answer that fast; ` +
|
|
99
|
+
`using ${MIN_POLL_INTERVAL_MS}ms instead.`);
|
|
100
|
+
}
|
|
101
|
+
const interval = Math.max(requested, MIN_POLL_INTERVAL_MS);
|
|
102
|
+
const deadline = this.now() + timeoutMs;
|
|
103
|
+
for (;;) {
|
|
104
|
+
const verdict = waitVerdict(await this.getAlert(customId));
|
|
105
|
+
if (verdict.stop)
|
|
106
|
+
return verdict.decision;
|
|
107
|
+
const remaining = deadline - this.now();
|
|
108
|
+
if (remaining <= 0) {
|
|
109
|
+
if (options.throwOnTimeout)
|
|
110
|
+
throw new M91TimeoutError(customId, timeoutMs);
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
await this.sleep(Math.min(interval, remaining));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Close an alert this link raised. Always `true` when there is nothing open.
|
|
118
|
+
*
|
|
119
|
+
* A monitor that sees the problem heal closes it itself rather than waiting
|
|
120
|
+
* for a person to wake up and notice.
|
|
121
|
+
*
|
|
122
|
+
* **404 and 403 are success.** "No such alert" and "not yours to close" both
|
|
123
|
+
* mean the alert is not open on this channel, which is the state the caller
|
|
124
|
+
* asked for. Anything else would make every close a branch, and every retry
|
|
125
|
+
* after a dropped response a false failure.
|
|
126
|
+
*/
|
|
127
|
+
async close(options) {
|
|
128
|
+
const { customId, alertId } = options ?? {};
|
|
129
|
+
if (Boolean(customId) === Boolean(alertId)) {
|
|
130
|
+
throw new M91ValidationError(`close() needs exactly one of customId or alertId — got ${customId ? 'both' : 'neither'}`);
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
await this.http.request({
|
|
134
|
+
method: 'POST',
|
|
135
|
+
url: this.url('/close'),
|
|
136
|
+
body: customId ? { customId } : { alertId },
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
if (error instanceof M91NotFoundError || error instanceof M91ForbiddenError)
|
|
141
|
+
return true;
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Ask a human, on their phone, and wait for the answer. `true` = approved.
|
|
148
|
+
*
|
|
149
|
+
* The shape of every human-in-the-loop gate: raise the question, block, act
|
|
150
|
+
* on the answer, close the alert either way. Severity defaults to HIGH here —
|
|
151
|
+
* unlike `send` — because something is *waiting* on the answer, so a phone
|
|
152
|
+
* call after 45 seconds of silence is the point rather than an escalation.
|
|
153
|
+
*
|
|
154
|
+
* Both labels are SHARED: approving and rejecting are each a final answer
|
|
155
|
+
* that settles the question for everybody, and neither is an "I saw it".
|
|
156
|
+
*
|
|
157
|
+
* On timeout with `timeoutMeansReject` (the default) it resolves false —
|
|
158
|
+
* nobody answered, so nothing consequential happens. Set it false to get
|
|
159
|
+
* `M91TimeoutError` instead. There is deliberately no way to make a timeout
|
|
160
|
+
* mean approved.
|
|
161
|
+
*/
|
|
162
|
+
async requestApproval(title, options) {
|
|
163
|
+
const { customId, description, severity = 'HIGH', approveLabel = 'Approve', rejectLabel = 'Reject', timeoutMs = 300_000, pollIntervalMs = 5000, timeoutMeansReject = true, } = options;
|
|
164
|
+
await this.send(title, {
|
|
165
|
+
description,
|
|
166
|
+
severity,
|
|
167
|
+
customId,
|
|
168
|
+
isAutoClose: false,
|
|
169
|
+
responseOptions: [
|
|
170
|
+
{ label: approveLabel, effect: 'SHARED' },
|
|
171
|
+
{ label: rejectLabel, effect: 'SHARED' },
|
|
172
|
+
],
|
|
173
|
+
});
|
|
174
|
+
try {
|
|
175
|
+
const decision = await this.waitForDecision(customId, { timeoutMs, pollIntervalMs });
|
|
176
|
+
if (decision === null) {
|
|
177
|
+
if (!timeoutMeansReject)
|
|
178
|
+
throw new M91TimeoutError(customId, timeoutMs);
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
return isApproved(decision, approveLabel);
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
// Answered or not, the question is no longer live. Left open, a rejected
|
|
185
|
+
// deploy keeps somebody's phone in the "needs attention" list for an
|
|
186
|
+
// action that already resolved.
|
|
187
|
+
await this.close({ customId });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* A 2xx that woke nobody is a failure, and only the caller can decide what to do.
|
|
193
|
+
*
|
|
194
|
+
* `delivered` counts phones actually sent to, so this fires when a channel's
|
|
195
|
+
* devices are all stale or its members have not installed the app — a case that
|
|
196
|
+
* answers 201 and looks exactly like success.
|
|
197
|
+
*/
|
|
198
|
+
export function checkDelivery(alert, enabled) {
|
|
199
|
+
if (!enabled || alert.actuallyReachedSomeone)
|
|
200
|
+
return;
|
|
201
|
+
throw new M91DeliveryError(`The alert was filed but reached nobody: delivered=0, ` +
|
|
202
|
+
`unregistered=${alert.unregistered.length}, noDevice=${alert.noDevice}, ` +
|
|
203
|
+
`pending=${alert.pending}. Nobody's phone rang.`, alert.delivered, alert.unregistered, alert.noDevice, alert.pending);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Whether one poll ends the wait. Pure, so the rule lives in one place.
|
|
207
|
+
*/
|
|
208
|
+
export function waitVerdict(detail) {
|
|
209
|
+
// Not visible yet. A send answers before the fan-out finishes, so an
|
|
210
|
+
// immediate read can outrun it; keep waiting rather than calling it gone.
|
|
211
|
+
if (detail === null)
|
|
212
|
+
return { stop: false, decision: null };
|
|
213
|
+
if (detail.decision !== null)
|
|
214
|
+
return { stop: true, decision: detail.decision };
|
|
215
|
+
// Closed with nobody having claimed it — by a monitor that saw the problem
|
|
216
|
+
// heal, or by hand. There is no answer coming.
|
|
217
|
+
if (detail.isClosed)
|
|
218
|
+
return { stop: true, decision: null };
|
|
219
|
+
return { stop: false, decision: null };
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every failure the SDK can throw, and the one place an HTTP response becomes one.
|
|
3
|
+
*
|
|
4
|
+
* Branch on `.code` — a stable string the API contract pins down — never on
|
|
5
|
+
* `.message`, which is written for a person reading a terminal and is expected
|
|
6
|
+
* to change.
|
|
7
|
+
*/
|
|
8
|
+
export declare class M91Error extends Error {
|
|
9
|
+
readonly code: string;
|
|
10
|
+
readonly statusCode?: number;
|
|
11
|
+
readonly details?: unknown;
|
|
12
|
+
constructor(message: string, code?: string, statusCode?: number, details?: unknown);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 400/413. The request was wrong, and it is never retried.
|
|
16
|
+
*
|
|
17
|
+
* Also thrown locally, before any HTTP call, when a field breaks a documented
|
|
18
|
+
* limit. Every schema server-side is `.strict()`, so a mistyped field name is a
|
|
19
|
+
* 400 rather than a silent drop — and a round trip to learn that is a round
|
|
20
|
+
* trip wasted.
|
|
21
|
+
*/
|
|
22
|
+
export declare class M91ValidationError extends M91Error {
|
|
23
|
+
constructor(message: string, details?: unknown, statusCode?: number, code?: string);
|
|
24
|
+
}
|
|
25
|
+
/** 401. Credential invalid, expired or revoked — nothing is cached server-side. */
|
|
26
|
+
export declare class M91AuthError extends M91Error {
|
|
27
|
+
constructor(message?: string, details?: unknown);
|
|
28
|
+
}
|
|
29
|
+
/** 403. Authenticated, but not allowed. On `close()` this means success. */
|
|
30
|
+
export declare class M91ForbiddenError extends M91Error {
|
|
31
|
+
constructor(message?: string, details?: unknown);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 404. Absent — **or present and not yours**. The API answers the same way to
|
|
35
|
+
* both on purpose, so an id cannot become an oracle for what exists.
|
|
36
|
+
*/
|
|
37
|
+
export declare class M91NotFoundError extends M91Error {
|
|
38
|
+
constructor(message?: string, details?: unknown);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 409. The channel has nobody who can actually be woken.
|
|
42
|
+
*
|
|
43
|
+
* `unregistered` is the invited numbers with no M91 account — invited, never
|
|
44
|
+
* installed the app, and unreachable. An ordinary SMS would not pierce Do Not
|
|
45
|
+
* Disturb, would not repeat, and would not escalate.
|
|
46
|
+
*/
|
|
47
|
+
export declare class M91ChannelEmptyError extends M91Error {
|
|
48
|
+
readonly unregistered: string[];
|
|
49
|
+
constructor(message: string, unregistered?: string[], details?: unknown);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 429. 60 sends a minute per send link.
|
|
53
|
+
*
|
|
54
|
+
* `retryAfter` always has a value: the server sets `Retry-After` on every 429
|
|
55
|
+
* precisely so a caller does not have to guess, and the usual guess is to retry
|
|
56
|
+
* immediately and forever.
|
|
57
|
+
*/
|
|
58
|
+
export declare class M91RateLimitError extends M91Error {
|
|
59
|
+
readonly retryAfter: number;
|
|
60
|
+
constructor(message?: string, retryAfter?: number, details?: unknown);
|
|
61
|
+
}
|
|
62
|
+
/** 5xx. Not the caller's fault. Retried with backoff before it reaches you. */
|
|
63
|
+
export declare class M91ServerError extends M91Error {
|
|
64
|
+
constructor(message?: string, statusCode?: number, details?: unknown);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The request never got an answer — DNS, TLS, connect or read timeout.
|
|
68
|
+
*
|
|
69
|
+
* Not retried. A send that timed out may or may not have raised an alert, and
|
|
70
|
+
* retrying it blindly is how one condition wakes a team four times.
|
|
71
|
+
*/
|
|
72
|
+
export declare class M91NetworkError extends M91Error {
|
|
73
|
+
constructor(message?: string, details?: unknown);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The API said 2xx and **nobody's phone rang**.
|
|
77
|
+
*
|
|
78
|
+
* The failure mode integrations actually hit, and it is invisible if you only
|
|
79
|
+
* check the status code. `delivered` counts phones sent to, not the size of the
|
|
80
|
+
* roster: a channel of four whose device tokens are all stale answers 201 with
|
|
81
|
+
* `delivered: 0`.
|
|
82
|
+
*
|
|
83
|
+
* A *deduplicated* send is not this error — `delivered` is 0 because the same
|
|
84
|
+
* alert is already open and already ringing, which is success.
|
|
85
|
+
*/
|
|
86
|
+
export declare class M91DeliveryError extends M91Error {
|
|
87
|
+
readonly delivered: number;
|
|
88
|
+
readonly unregistered: string[];
|
|
89
|
+
readonly noDevice: number;
|
|
90
|
+
readonly pending: number;
|
|
91
|
+
constructor(message: string, delivered?: number, unregistered?: string[], noDevice?: number, pending?: number);
|
|
92
|
+
}
|
|
93
|
+
/** `waitForDecision` ran out of time with no SHARED response. Nobody answered. */
|
|
94
|
+
export declare class M91TimeoutError extends M91Error {
|
|
95
|
+
readonly customId: string;
|
|
96
|
+
readonly timeoutMs: number;
|
|
97
|
+
constructor(customId: string, timeoutMs: number);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Turn one HTTP response into either the payload or a thrown error.
|
|
101
|
+
*
|
|
102
|
+
* **Every M91 route answers in an envelope** — `{ success: true, data }` or
|
|
103
|
+
* `{ success: false, error: { code, message, details } }` — and this is the only
|
|
104
|
+
* place the SDK knows that. Everything above this line sees the inner `data`.
|
|
105
|
+
*
|
|
106
|
+
* The error class is chosen by HTTP status rather than by `error.code`, because
|
|
107
|
+
* the status is what the server derives the code from in the first place;
|
|
108
|
+
* keeping them in step in two places is how they drift apart. The body's `code`
|
|
109
|
+
* is still carried onto the error.
|
|
110
|
+
*/
|
|
111
|
+
export declare function unwrap(status: number, body: unknown, headers?: Headers | Record<string, string>): unknown;
|
|
112
|
+
/** Seconds from `Retry-After`, or undefined. Header lookups are case-insensitive. */
|
|
113
|
+
export declare function retryAfter(headers?: Headers | Record<string, string>): number | undefined;
|
|
114
|
+
//# sourceMappingURL=exceptions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exceptions.d.ts","sourceRoot":"","sources":["../../src/exceptions.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;gBAEf,OAAO,EAAE,MAAM,EAAE,IAAI,SAAc,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO;CAUxF;AAED;;;;;;;GAOG;AACH,qBAAa,kBAAmB,SAAQ,QAAQ;gBAClC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,UAAU,SAAM,EAAE,IAAI,SAAqB;CAG5F;AAED,mFAAmF;AACnF,qBAAa,YAAa,SAAQ,QAAQ;gBAC5B,OAAO,SAAiB,EAAE,OAAO,CAAC,EAAE,OAAO;CAGxD;AAED,4EAA4E;AAC5E,qBAAa,iBAAkB,SAAQ,QAAQ;gBACjC,OAAO,SAAc,EAAE,OAAO,CAAC,EAAE,OAAO;CAGrD;AAED;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,QAAQ;gBAChC,OAAO,SAAc,EAAE,OAAO,CAAC,EAAE,OAAO;CAGrD;AAED;;;;;;GAMG;AACH,qBAAa,oBAAqB,SAAQ,QAAQ;IAChD,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC;gBAEpB,OAAO,EAAE,MAAM,EAAE,YAAY,GAAE,MAAM,EAAO,EAAE,OAAO,CAAC,EAAE,OAAO;CAI5E;AAED;;;;;;GAMG;AACH,qBAAa,iBAAkB,SAAQ,QAAQ;IAC7C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAEhB,OAAO,SAAiB,EAAE,UAAU,SAAK,EAAE,OAAO,CAAC,EAAE,OAAO;CAIzE;AAED,+EAA+E;AAC/E,qBAAa,cAAe,SAAQ,QAAQ;gBAC9B,OAAO,SAA0B,EAAE,UAAU,SAAM,EAAE,OAAO,CAAC,EAAE,OAAO;CAGnF;AAED;;;;;GAKG;AACH,qBAAa,eAAgB,SAAQ,QAAQ;gBAC/B,OAAO,SAAkB,EAAE,OAAO,CAAC,EAAE,OAAO;CAGzD;AAED;;;;;;;;;;GAUG;AACH,qBAAa,gBAAiB,SAAQ,QAAQ;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAGvB,OAAO,EAAE,MAAM,EACf,SAAS,SAAI,EACb,YAAY,GAAE,MAAM,EAAO,EAC3B,QAAQ,SAAI,EACZ,OAAO,SAAI;CAQd;AAED,kFAAkF;AAClF,qBAAa,eAAgB,SAAQ,QAAQ;IAC3C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;CAQhD;AAQD;;;;;;;;;;;GAWG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CA8BzG;AAOD,qFAAqF;AACrF,wBAAgB,UAAU,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,GAAG,SAAS,CAWzF"}
|