@roxy-agent/agents 0.1.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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +306 -0
  3. package/dist/approvals.js +143 -0
  4. package/dist/approvals.js.map +1 -0
  5. package/dist/classifier.js +436 -0
  6. package/dist/classifier.js.map +1 -0
  7. package/dist/dashboard/client.js +2057 -0
  8. package/dist/dashboard/client.js.map +1 -0
  9. package/dist/dashboard/html.js +57 -0
  10. package/dist/dashboard/html.js.map +1 -0
  11. package/dist/dashboard/icons.js +18 -0
  12. package/dist/dashboard/icons.js.map +1 -0
  13. package/dist/dashboard/server.js +423 -0
  14. package/dist/dashboard/server.js.map +1 -0
  15. package/dist/dashboard/styles.js +1685 -0
  16. package/dist/dashboard/styles.js.map +1 -0
  17. package/dist/dashboard.js +2 -0
  18. package/dist/dashboard.js.map +1 -0
  19. package/dist/db.js +526 -0
  20. package/dist/db.js.map +1 -0
  21. package/dist/index.js +94 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/license.js +257 -0
  24. package/dist/license.js.map +1 -0
  25. package/dist/logger.js +44 -0
  26. package/dist/logger.js.map +1 -0
  27. package/dist/ml/bash-classifier.js +121 -0
  28. package/dist/ml/bash-classifier.js.map +1 -0
  29. package/dist/ml/embedder.js +79 -0
  30. package/dist/ml/embedder.js.map +1 -0
  31. package/dist/ml/prototypes.js +707 -0
  32. package/dist/ml/prototypes.js.map +1 -0
  33. package/dist/policies.js +289 -0
  34. package/dist/policies.js.map +1 -0
  35. package/dist/slack.js +149 -0
  36. package/dist/slack.js.map +1 -0
  37. package/dist/tools/bash.js +134 -0
  38. package/dist/tools/bash.js.map +1 -0
  39. package/dist/tools/conversation.js +36 -0
  40. package/dist/tools/conversation.js.map +1 -0
  41. package/dist/tools/filesystem.js +243 -0
  42. package/dist/tools/filesystem.js.map +1 -0
  43. package/dist/tools/introspect.js +187 -0
  44. package/dist/tools/introspect.js.map +1 -0
  45. package/dist/tools/network.js +152 -0
  46. package/dist/tools/network.js.map +1 -0
  47. package/dist/tools/policies.js +107 -0
  48. package/dist/tools/policies.js.map +1 -0
  49. package/package.json +61 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 David Wang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,306 @@
1
+ # @roxy-agent/agents
2
+
3
+ An MCP server that proxies all agent actions — bash, filesystem, network — through a risk classifier and a local SQLite audit log, with a live web dashboard at `http://localhost:4242`.
4
+
5
+ ## Quick start
6
+
7
+ Add this to your MCP config (`~/.cursor/mcp.json` for Cursor, `~/Library/Application Support/Claude/claude_desktop_config.json` for Claude Desktop, the equivalent for Codex):
8
+
9
+ ```json
10
+ {
11
+ "mcpServers": {
12
+ "agent-proxy": {
13
+ "command": "npx",
14
+ "args": ["-y", "@roxy-agent/agents"]
15
+ }
16
+ }
17
+ }
18
+ ```
19
+
20
+ Then reload MCP servers (or restart the host). On first launch `npx` downloads the package, the SQLite audit DB is created at `~/.agent-proxy/audit.db`, the dashboard starts at [`http://localhost:4242`](http://localhost:4242), and the ML model (~25 MB) downloads to `~/.agent-proxy/models/` in the background.
21
+
22
+ No clone, no global install, no path configuration required.
23
+
24
+ ## What it does
25
+
26
+ Every tool call is:
27
+
28
+ 1. **Classified** — assigned a risk level (`low` / `medium` / `high`) and a decision (`allowed` / `flagged` / `denied`).
29
+ 2. **Logged** — written to `data/audit.db` before execution, then updated with the result, duration, and error.
30
+ 3. **Mirrored** — printed to stderr and surfaced in the live dashboard so you can see what the agent is doing in real time.
31
+
32
+ Hard‑deny patterns (e.g. `rm -rf /`, `curl … | bash`, `mkfs.*`) are never executed — the call returns immediately with `blocked: true`.
33
+
34
+ ## Bash classifier — rules + ML
35
+
36
+ Bash commands go through a two-stage classifier:
37
+
38
+ 1. **Regex hard-deny pre-filter** (deterministic, ~µs). Patterns like `rm -rf /`,
39
+ fork bombs, `dd of=/dev/…`, and `curl … | bash` are *always* denied. ML never
40
+ sees them and never has the chance to downgrade them.
41
+ 2. **Embedding-based k-NN classifier** (real ML, fully local). Commands are
42
+ embedded with [`Xenova/all-MiniLM-L6-v2`](https://huggingface.co/Xenova/all-MiniLM-L6-v2)
43
+ (~25 MB, quantized ONNX) running in-process via
44
+ [`@huggingface/transformers`](https://github.com/huggingface/transformers.js)
45
+ and scored against ~110 labeled prototype commands in
46
+ [`src/ml/prototypes.ts`](src/ml/prototypes.ts). The model and prototypes
47
+ load in the background; calls that arrive before it's ready transparently
48
+ fall back to the rule-based classifier.
49
+
50
+ The ML output and the rule output are then combined by taking **whichever is
51
+ more conservative**, so the model can escalate but never silently downgrade.
52
+
53
+ This catches commands that look benign character-by-character but are
54
+ clearly destructive in intent. For example:
55
+
56
+ | Command | Rules | ML |
57
+ | --- | --- | --- |
58
+ | `please nuke every file in this folder` | low / allowed | **high / flagged** (≈ "wipe everything in this folder") |
59
+ | `recursively obliterate node_modules` | low / allowed | **high / flagged** (≈ "rm -rf node_modules") |
60
+ | `publish this package to npm` | low / allowed | **high / flagged** (≈ "npm publish") |
61
+ | `drop all rows from the users table` | low / allowed | **high / flagged** (≈ "TRUNCATE TABLE users") |
62
+ | `wipe the entire git history and force push` | low / allowed | **high / flagged** (≈ "git push --force") |
63
+
64
+ The model files are cached in `data/models/` after first run. To disable ML
65
+ entirely (and use only the regex rules), set `AGENT_PROXY_ML=0`.
66
+
67
+ Try it:
68
+
69
+ ```bash
70
+ npm run build
71
+ node scripts/ml-smoke.mjs
72
+ ```
73
+
74
+ ## Policies — natural-language allow / block rules
75
+
76
+ Teams can attach an arbitrary number of allow / block policies through the
77
+ dashboard. Policies are written in plain English, embedded with the same
78
+ sentence-transformer used by the bash classifier, and matched against every
79
+ incoming bash / filesystem / network action via cosine similarity.
80
+
81
+ Precedence inside the classifier:
82
+
83
+ 1. **Regex hard-deny** (`rm -rf /`, `curl … | bash`, etc.) — never overridable.
84
+ 2. **BLOCK policy** match — denies the call.
85
+ 3. **ALLOW policy** match — forces low / allowed (de-escalates flagged actions).
86
+ 4. **Rules + ML** (the existing combined classifier).
87
+
88
+ Examples that work today:
89
+
90
+ | Policy | What it does |
91
+ | --- | --- |
92
+ | `"Never destroy infrastructure with terraform or pulumi."` | Blocks `terraform destroy -auto-approve` (sim ≈ 0.69). |
93
+ | `"Always allow git status, git log, git diff, and git branch."` | De-escalates inspection-only git commands (sim ≈ 0.69). |
94
+ | `"Block any kubectl command."` | Blocks `kubectl get pods -n production` (sim ≈ 0.49). |
95
+ | `"It's fine to read any file inside the project's data folder."` | Allows reads of `data/**`. |
96
+
97
+ Tips for writing good policies:
98
+
99
+ - **Be focused.** "Block all AWS commands" matches `aws s3 ls` better than
100
+ "Never run any AWS or kubectl commands against production." — fewer
101
+ concepts per policy keeps the embedding tight.
102
+ - **Use the dashboard tester.** Type a candidate command and watch the live
103
+ similarity scores update. Anything ≥ 0.40 by default will fire.
104
+ - **Tune the threshold.** Set `AGENT_PROXY_POLICY_THRESHOLD=0.35` to be
105
+ more permissive, or `0.55` to require very tight matches.
106
+
107
+ REST API (also used by the dashboard):
108
+
109
+ ```
110
+ GET /api/policies # list all
111
+ POST /api/policies # { kind, description, applies_to?, scope? }
112
+ PATCH /api/policies/:id # partial update — re-embeds if description changes
113
+ DELETE /api/policies/:id
114
+ POST /api/policies/test # { text, tool } → top-N similarities (no side effects)
115
+ ```
116
+
117
+ To disable policy matching entirely set `AGENT_PROXY_POLICIES=0`. To scope
118
+ policies (e.g. one Cursor session is "team:eng" while another is "team:sec"),
119
+ set `AGENT_PROXY_SCOPES=team:eng,global` — only policies whose scope is in
120
+ that set will be evaluated.
121
+
122
+ ## Tools exposed over MCP
123
+
124
+ **Side-effect tools** — every call is classified and audited:
125
+
126
+ | Tool | Purpose |
127
+ | --- | --- |
128
+ | `bash` | Run a shell command. Classified per command. |
129
+ | `read_file` | Read a UTF‑8 file. Sensitive paths flagged. |
130
+ | `write_file` | Write/append a file. Writes to sensitive paths denied. |
131
+ | `delete_file` | Delete a file. Always flagged. |
132
+ | `list_directory` | List a directory. |
133
+ | `fetch_url` | Make an HTTP(S) request. External hosts flagged, executable script downloads denied. |
134
+
135
+ **Introspection tools** — read-only, no audit entry, designed for the agent to plan and self-audit:
136
+
137
+ | Tool | Purpose |
138
+ | --- | --- |
139
+ | `classify` | Pre-flight risk check. Returns the decision (`allowed`/`flagged`/`denied`) the proxy *would* return for a hypothetical bash command, filesystem path, or URL — without executing it. Use this before committing to a destructive op. |
140
+ | `recent_events` | Read the audit log. Filter by tool, decision, or session. Pass `session_id: "current"` to summarize what the agent has done so far. |
141
+ | `proxy_stats` | Heartbeat / aggregate counters (totals, ML readiness, current session, dashboard URL). |
142
+
143
+ **Policy management tools** — persist user-stated guardrails across sessions:
144
+
145
+ | Tool | Purpose |
146
+ | --- | --- |
147
+ | `add_policy` | Create a natural-language allow or block rule. Use when the user says "never push to main" or "always allow npm install in this repo" — the description is embedded and matched semantically against future calls. |
148
+ | `list_policies` | List every persisted policy. With `against: "<text>"` it scores all policies against a hypothetical action and returns them sorted by similarity, so the agent can see which rule will fire and at what threshold. |
149
+ | `update_policy` | Modify an existing policy by id (toggle `enabled`, retighten the description, scope it to a tool, etc.). |
150
+ | `delete_policy` | Delete a policy by id. Prefer disabling via `update_policy` so it can be re-enabled later. |
151
+
152
+ The agent is encouraged to use `classify` proactively whenever it's about to run a command that *might* be destructive or surprising. Pre-flight is much cheaper than rolling back a denied tool call mid-task.
153
+
154
+ ## Run from source (for local development)
155
+
156
+ ```bash
157
+ git clone https://github.com/<you>/agent-proxy.git
158
+ cd agent-proxy
159
+ npm install
160
+ npm run build
161
+ npm start
162
+ ```
163
+
164
+ Or for development with auto-reload:
165
+
166
+ ```bash
167
+ npm run dev
168
+ ```
169
+
170
+ The dashboard starts at `http://localhost:4242` (override with `AGENT_PROXY_PORT`). When run from a source checkout, the audit DB and model cache stay in `<repo>/data/` to keep your dev install separate from the user-level install at `~/.agent-proxy/`.
171
+
172
+ The dashboard is a four-tab single-page app:
173
+
174
+ - **Overview** — today's KPIs, 24h sparkline, recent activity, top firing policies
175
+ - **Activity** — full event log with search, tool/decision filters, time range, and a drawer that shows the full payload, classifier reasoning, and adjacent session events
176
+ - **Policies** — manage natural-language allow/block rules; inline tester scores any command against every policy with a similarity bar
177
+ - **Insights** — stacked area chart of allowed/flagged/denied per hour, tool & decision distributions, ML model stats, top denied/flagged actions, recent sessions
178
+
179
+ Set in [Geist Sans + Geist Mono + Geist Pixel](https://vercel.com/font), self-hosted from the `geist` npm package — no Google Fonts dependency, works offline. Includes a `⌘K` command palette and `g o`/`g a`/`g p`/`g i` keyboard navigation.
180
+
181
+ ## MCP config — alternatives
182
+
183
+ The recommended config is `npx -y @roxy-agent/agents` (see Quick start above). You can also point your MCP host at a local checkout:
184
+
185
+ **Built from source:**
186
+
187
+ ```json
188
+ {
189
+ "mcpServers": {
190
+ "agent-proxy": {
191
+ "command": "node",
192
+ "args": ["/absolute/path/to/agent-proxy/dist/index.js"]
193
+ }
194
+ }
195
+ }
196
+ ```
197
+
198
+ **Source mode (no build step), useful while iterating:**
199
+
200
+ ```json
201
+ {
202
+ "mcpServers": {
203
+ "agent-proxy": {
204
+ "command": "npx",
205
+ "args": ["tsx", "/absolute/path/to/agent-proxy/src/index.ts"]
206
+ }
207
+ }
208
+ }
209
+ ```
210
+
211
+ **Pin a specific version** (recommended for teams to avoid surprise updates):
212
+
213
+ ```json
214
+ {
215
+ "mcpServers": {
216
+ "agent-proxy": {
217
+ "command": "npx",
218
+ "args": ["-y", "@roxy-agent/agents@0.1.0"]
219
+ }
220
+ }
221
+ }
222
+ ```
223
+
224
+ ## Project rules snippet
225
+
226
+ Drop this into a project's `AGENTS.md` or `.cursor/rules/` to nudge the agent toward the proxy:
227
+
228
+ ```markdown
229
+ You have access to an `agent-proxy` MCP server. Use it for all side effects:
230
+
231
+ - Use the `bash` tool from agent-proxy instead of running shell commands directly.
232
+ - Use `read_file`, `write_file`, `delete_file`, `list_directory` instead of direct file ops.
233
+ - Use `fetch_url` for any HTTP requests.
234
+
235
+ All actions are logged. The audit dashboard is at http://localhost:4242.
236
+ ```
237
+
238
+ ## Environment variables
239
+
240
+ | Variable | Default | Purpose |
241
+ | --- | --- | --- |
242
+ | `AGENT_PROXY_PORT` | `4242` | Dashboard HTTP port. |
243
+ | `AGENT_PROXY_DATA_DIR` | `~/.agent-proxy` | Where the audit DB, license, and model cache live. Falls back to `<repo>/data/` if you launched from a source checkout that already has one. |
244
+ | `AGENT_PROXY_SESSION_ID` | random UUID | Override the session id (handy for per‑task traceability). |
245
+ | `AGENT_PROXY_DISABLE_DASHBOARD` | unset | Set to `1` to skip starting the dashboard. |
246
+ | `AGENT_PROXY_ML` | enabled | Set to `0` to disable the embedding-based bash classifier and use rules only. |
247
+ | `AGENT_PROXY_ML_MODEL` | `Xenova/all-MiniLM-L6-v2` | Hugging Face model id used for the bash classifier embedder. |
248
+ | `AGENT_PROXY_ML_CACHE` | `~/.agent-proxy/models` | Where transformers.js caches model files. |
249
+ | `AGENT_PROXY_POLICIES` | enabled | Set to `0` to disable natural-language policies. |
250
+ | `AGENT_PROXY_POLICY_THRESHOLD` | `0.40` | Minimum cosine similarity for a policy to match. |
251
+ | `AGENT_PROXY_SCOPES` | `global` | Comma-separated list of scopes whose policies are active in this session. |
252
+
253
+ ## Layout
254
+
255
+ ```
256
+ src/
257
+ index.ts # MCP server entry point
258
+ db.ts # SQLite + queries
259
+ classifier.ts # rule-based + ML + policy combined classifier
260
+ policies.ts # natural-language allow/block policies (CRUD + matching)
261
+ logger.ts # logEvent + updateEventResult, mirrors to stderr
262
+ dashboard.ts # Express dashboard (HTML + JSON API)
263
+ tools/
264
+ bash.ts
265
+ filesystem.ts
266
+ network.ts
267
+ ml/
268
+ embedder.ts # transformers.js feature-extraction pipeline
269
+ prototypes.ts # labeled bash command prototypes
270
+ bash-classifier.ts # k-NN classifier over embedded prototypes
271
+ data/
272
+ audit.db # auto-created (events + policies tables)
273
+ models/ # cached ONNX model files
274
+ ```
275
+
276
+ ## Audit DB schema
277
+
278
+ ```sql
279
+ CREATE TABLE events (
280
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
281
+ timestamp TEXT NOT NULL,
282
+ session_id TEXT NOT NULL,
283
+ tool TEXT NOT NULL,
284
+ action_type TEXT NOT NULL,
285
+ payload TEXT NOT NULL, -- JSON
286
+ risk_level TEXT NOT NULL, -- low|medium|high
287
+ decision TEXT NOT NULL, -- allowed|denied|flagged
288
+ result TEXT, -- summary string
289
+ duration_ms INTEGER,
290
+ error TEXT,
291
+ reason TEXT -- classifier reason
292
+ );
293
+
294
+ CREATE TABLE policies (
295
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
296
+ kind TEXT NOT NULL, -- allow|block
297
+ description TEXT NOT NULL, -- natural language
298
+ scope TEXT NOT NULL DEFAULT 'global',
299
+ applies_to TEXT NOT NULL DEFAULT '*', -- bash|filesystem|network|*
300
+ enabled INTEGER NOT NULL DEFAULT 1,
301
+ match_count INTEGER NOT NULL DEFAULT 0,
302
+ embedding BLOB, -- 384-dim float32, lazily filled
303
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
304
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
305
+ );
306
+ ```
@@ -0,0 +1,143 @@
1
+ // Human-in-the-loop approval orchestrator.
2
+ //
3
+ // When the classifier returns `pending_approval`, the calling tool blocks here
4
+ // until a reviewer approves or denies the action via the dashboard, Slack, or
5
+ // the configured timeout fires. Pending state is persisted to SQLite so the
6
+ // dashboard can list it; resolution is signaled in-process via a Promise map.
7
+ //
8
+ // IMPORTANT: pending approvals do NOT survive a server restart (the awaiting
9
+ // promise dies with the process). On boot, `expireOrphanApprovals` marks any
10
+ // stale rows as `timeout` so the dashboard reflects reality.
11
+ import { createApprovalRow, setApprovalStatus, getApproval, countPendingApprovals, expireOrphanApprovals, updateEventDecision, } from "./db.js";
12
+ const pending = new Map();
13
+ function approvalTimeoutMs() {
14
+ const env = Number(process.env.AGENT_PROXY_APPROVAL_TIMEOUT_MS);
15
+ if (Number.isFinite(env) && env > 0)
16
+ return env;
17
+ return 5 * 60 * 1000;
18
+ }
19
+ export function initApprovals() {
20
+ const expired = expireOrphanApprovals();
21
+ if (expired > 0) {
22
+ process.stderr.write(`[agent-proxy] expired ${expired} orphan approval(s) from previous run\n`);
23
+ }
24
+ }
25
+ export function pendingCount() {
26
+ return countPendingApprovals();
27
+ }
28
+ // Block the calling tool until a reviewer (dashboard / Slack) decides.
29
+ // Resolves with `{ approved: true }` to let the tool proceed, or
30
+ // `{ approved: false }` if denied or timed out — tool should treat it as
31
+ // blocked and surface the reason to the agent.
32
+ export async function awaitApproval(input) {
33
+ const id = createApprovalRow({
34
+ event_id: input.event_id ?? null,
35
+ tool: input.tool,
36
+ summary: input.summary,
37
+ reason: input.reason,
38
+ payload: input.payload,
39
+ session_id: input.session_id ?? null,
40
+ });
41
+ // Notify Slack best-effort (don't block on it).
42
+ void (async () => {
43
+ try {
44
+ const { notifyApprovalRequest } = await import("./slack.js");
45
+ const row = getApproval(id);
46
+ if (row)
47
+ await notifyApprovalRequest(row);
48
+ }
49
+ catch { }
50
+ })();
51
+ return new Promise((resolve) => {
52
+ const timer = setTimeout(() => {
53
+ if (!pending.has(id))
54
+ return;
55
+ pending.delete(id);
56
+ clearInterval(poller);
57
+ const ok = setApprovalStatus(id, "timeout", "timeout");
58
+ if (!ok)
59
+ return;
60
+ try {
61
+ updateEventDecision(input.event_id, "denied", `approval timed out after ${Math.round(approvalTimeoutMs() / 1000)}s`);
62
+ }
63
+ catch { }
64
+ resolve({
65
+ approved: false,
66
+ status: "timeout",
67
+ by: "timeout",
68
+ reason: `approval timed out after ${Math.round(approvalTimeoutMs() / 1000)}s`,
69
+ });
70
+ }, approvalTimeoutMs());
71
+ // Cross-process fallback: if some other process (e.g. a separately-running
72
+ // dashboard) resolved the approval directly in the DB, the in-memory
73
+ // resolve() above is never called. Poll the row every 500ms and resolve
74
+ // ourselves if we see a non-pending status.
75
+ const poller = setInterval(() => {
76
+ const row = getApproval(id);
77
+ if (!row || row.status === "pending")
78
+ return;
79
+ const entry = pending.get(id);
80
+ if (!entry)
81
+ return;
82
+ pending.delete(id);
83
+ clearTimeout(timer);
84
+ clearInterval(poller);
85
+ const status = row.status;
86
+ const by = row.decided_by ?? "unknown";
87
+ try {
88
+ updateEventDecision(input.event_id, status === "approved" ? "allowed" : "denied", status === "approved" ? `approved by ${by}` : `${status} by ${by}`);
89
+ }
90
+ catch { }
91
+ resolve({
92
+ approved: status === "approved",
93
+ status,
94
+ by,
95
+ reason: status === "approved" ? `approved by ${by}` : `${status} by ${by}`,
96
+ });
97
+ }, 500);
98
+ pending.set(id, { timer, poller, input, resolve });
99
+ });
100
+ }
101
+ // Called by the dashboard / Slack receiver to resolve a pending approval.
102
+ // Returns the updated row, or undefined if the id was unknown / already
103
+ // resolved.
104
+ export function decideApproval(id, decision, by) {
105
+ const row = getApproval(id);
106
+ if (!row)
107
+ return undefined;
108
+ if (row.status !== "pending")
109
+ return row;
110
+ const ok = setApprovalStatus(id, decision, by);
111
+ if (!ok)
112
+ return getApproval(id);
113
+ const entry = pending.get(id);
114
+ if (entry) {
115
+ clearTimeout(entry.timer);
116
+ if (entry.poller)
117
+ clearInterval(entry.poller);
118
+ pending.delete(id);
119
+ try {
120
+ updateEventDecision(entry.input.event_id, decision === "approved" ? "allowed" : "denied", decision === "approved"
121
+ ? `approved by ${by}`
122
+ : `denied by ${by}`);
123
+ }
124
+ catch { }
125
+ entry.resolve({
126
+ approved: decision === "approved",
127
+ status: decision,
128
+ by,
129
+ reason: decision === "approved" ? `approved by ${by}` : `denied by ${by}`,
130
+ });
131
+ }
132
+ else if (row.event_id) {
133
+ // No in-process awaiter (e.g. cross-process resolution after a restart).
134
+ try {
135
+ updateEventDecision(row.event_id, decision === "approved" ? "allowed" : "denied", decision === "approved"
136
+ ? `approved by ${by}`
137
+ : `denied by ${by}`);
138
+ }
139
+ catch { }
140
+ }
141
+ return getApproval(id);
142
+ }
143
+ //# sourceMappingURL=approvals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approvals.js","sourceRoot":"","sources":["../src/approvals.ts"],"names":[],"mappings":"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"}