ardea 0.2.1 → 0.3.1
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/LICENSE +1 -1
- package/README.md +28 -76
- package/dist/chunk-OREJ23CV.cjs +7 -0
- package/dist/chunk-OREJ23CV.cjs.map +1 -0
- package/dist/chunk-UL6WUTYP.js +7 -0
- package/dist/chunk-UL6WUTYP.js.map +1 -0
- package/dist/index.cjs +396 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +175 -0
- package/dist/index.d.ts +175 -0
- package/dist/index.js +396 -0
- package/dist/index.js.map +1 -0
- package/dist/journal/server.cjs +571 -0
- package/dist/journal/server.cjs.map +1 -0
- package/dist/journal/server.d.cts +57 -0
- package/dist/journal/server.d.ts +57 -0
- package/dist/{server.js → journal/server.js} +30 -146
- package/dist/journal/server.js.map +1 -0
- package/package.json +20 -8
- package/dist/server.js.map +0 -1
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,35 +1,10 @@
|
|
|
1
|
-
# ardea
|
|
1
|
+
# ardea
|
|
2
2
|
|
|
3
|
-
MCP
|
|
3
|
+
MCP tools for AI agents to report API experiences. Record observations during tasks and submit structured feedback that gets scored and surfaced in the [Ardea dashboard](https://ardea-production.up.railway.app).
|
|
4
4
|
|
|
5
|
-
## Quick
|
|
5
|
+
## Quick Start
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
claude mcp add ardea -- npx -y ardea
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
**Or paste this into Claude Code:**
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
Add the Ardea API feedback MCP to my project.
|
|
17
|
-
Merge this into .mcp.json (create the file if it doesn't exist):
|
|
18
|
-
|
|
19
|
-
{
|
|
20
|
-
"mcpServers": {
|
|
21
|
-
"ardea": {
|
|
22
|
-
"command": "npx",
|
|
23
|
-
"args": ["-y", "ardea"]
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
Don't overwrite any existing servers in the file.
|
|
29
|
-
After editing, tell me to restart Claude Code so the MCP can start.
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
**Or manually** add to `.mcp.json`:
|
|
7
|
+
Add to your MCP config (`.mcp.json`, Claude Desktop, etc.):
|
|
33
8
|
|
|
34
9
|
```json
|
|
35
10
|
{
|
|
@@ -42,64 +17,41 @@ After editing, tell me to restart Claude Code so the MCP can start.
|
|
|
42
17
|
}
|
|
43
18
|
```
|
|
44
19
|
|
|
45
|
-
|
|
20
|
+
On first run, a browser window opens for signup. Your API key is saved to `~/.ardea/config.json` automatically.
|
|
46
21
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
1. **Install** — add the MCP config above, restart Claude Code
|
|
50
|
-
2. **First tool call** — Ardea checks for an API key:
|
|
51
|
-
- `ARDEA_API_KEY` environment variable
|
|
52
|
-
- `~/.ardea/config.json` (saved from previous signup)
|
|
53
|
-
3. **No key found?** — Your browser opens automatically to the signup page
|
|
54
|
-
4. **Sign up** — create your account in the browser
|
|
55
|
-
5. **Done** — API key is saved to `~/.ardea/config.json`. All future starts use it automatically.
|
|
22
|
+
## Tools
|
|
56
23
|
|
|
57
|
-
|
|
24
|
+
### `ardea_annotate`
|
|
58
25
|
|
|
59
|
-
|
|
60
|
-
{
|
|
61
|
-
"mcpServers": {
|
|
62
|
-
"ardea": {
|
|
63
|
-
"command": "npx",
|
|
64
|
-
"args": ["-y", "ardea"],
|
|
65
|
-
"env": {
|
|
66
|
-
"ARDEA_API_KEY": "ardea_sk_your_key_here"
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
```
|
|
26
|
+
Record API observations during a task. Stores notes locally in `~/.ardea/annotations/`.
|
|
72
27
|
|
|
73
|
-
|
|
28
|
+
- `id` — annotation key, e.g. `"stripe/charges"`
|
|
29
|
+
- `note` — what happened: result, errors, latency
|
|
30
|
+
- `list: true` — list all saved annotations
|
|
31
|
+
- `clear: true` — delete an annotation
|
|
74
32
|
|
|
75
|
-
|
|
33
|
+
### `ardea_report`
|
|
76
34
|
|
|
77
|
-
|
|
78
|
-
|------|---------|
|
|
79
|
-
| `ardea_annotate` | Record API observations as you work — endpoint, status code, errors, latency, surprises |
|
|
80
|
-
| `ardea_report` | Submit a structured feedback summary when your task is complete |
|
|
81
|
-
| `ardea_wallet` | Redeem vouchers, check credit balances, pay for API calls |
|
|
35
|
+
Submit a structured API experience report for scoring.
|
|
82
36
|
|
|
83
|
-
|
|
37
|
+
- `task_goal` (required) — what you were trying to do
|
|
38
|
+
- `outcome` (required) — `"success"`, `"partial"`, `"failure"`, or `"blocked"`
|
|
39
|
+
- `apis_used` (required) — APIs and endpoints used
|
|
40
|
+
- `what_worked` — what went well
|
|
41
|
+
- `what_failed` — what broke, with status codes and errors
|
|
42
|
+
- `friction` — biggest pain point
|
|
43
|
+
- `suggestion` — improvement recommendation
|
|
84
44
|
|
|
85
45
|
## Environment Variables
|
|
86
46
|
|
|
87
|
-
|
|
88
|
-
|----------|---------|-------------|
|
|
89
|
-
| `ARDEA_API_KEY` | (browser auth) | API key starting with `ardea_sk_` |
|
|
90
|
-
| `ARDEA_ENDPOINT` | production | Backend URL (override for local dev) |
|
|
91
|
-
| `ARDEA_SESSION_ID` | (auto) | Default session ID for reports |
|
|
92
|
-
| `ARDEA_AGENT_NAME` | (unnamed) | Agent name for multi-agent setups |
|
|
93
|
-
|
|
94
|
-
## CLI Flags
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
npx ardea --api-key=ardea_sk_... --endpoint=https://... --session-id=my-session --agent-name=my-agent
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
## Verify Installation
|
|
47
|
+
All optional — browser signup handles configuration automatically.
|
|
101
48
|
|
|
102
|
-
|
|
49
|
+
| Variable | Purpose |
|
|
50
|
+
|----------|---------|
|
|
51
|
+
| `ARDEA_API_KEY` | Skip browser signup |
|
|
52
|
+
| `ARDEA_ENDPOINT` | Custom backend URL (default: production) |
|
|
53
|
+
| `ARDEA_SESSION_ID` | Correlate reports to a session |
|
|
54
|
+
| `ARDEA_AGENT_NAME` | Label for this agent |
|
|
103
55
|
|
|
104
56
|
## License
|
|
105
57
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/stalapaneni/conductor/workspaces/canary/bucharest/canary/dist/chunk-OREJ23CV.cjs","../src/constants.ts"],"names":[],"mappings":"AAAA;ACAO,IAAM,iBAAA,EACX,yCAAA;ADCF;AACA;AACE;AACF,4CAAC","file":"/Users/stalapaneni/conductor/workspaces/canary/bucharest/canary/dist/chunk-OREJ23CV.cjs","sourcesContent":[null,"export const DEFAULT_ENDPOINT =\n \"https://ardea-production.up.railway.app\";\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts"],"sourcesContent":["export const DEFAULT_ENDPOINT =\n \"https://ardea-production.up.railway.app\";\n"],"mappings":";AAAO,IAAM,mBACX;","names":[]}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } var _class;
|
|
2
|
+
|
|
3
|
+
var _chunkOREJ23CVcjs = require('./chunk-OREJ23CV.cjs');
|
|
4
|
+
|
|
5
|
+
// src/buffer.ts
|
|
6
|
+
var _zlib = require('zlib');
|
|
7
|
+
var _https = require('https'); var _https2 = _interopRequireDefault(_https);
|
|
8
|
+
var _http = require('http'); var _http2 = _interopRequireDefault(_http);
|
|
9
|
+
var MAX_LEN = 1e4;
|
|
10
|
+
var BATCH_SIZE = 100;
|
|
11
|
+
var FLUSH_INTERVAL_MS = 1e4;
|
|
12
|
+
var MAX_EVENTS_PER_SESSION = 500;
|
|
13
|
+
var MAX_SESSION_CACHE_EVENTS = 5e3;
|
|
14
|
+
var RETRY_ATTEMPTS = 3;
|
|
15
|
+
var RETRY_BACKOFF_MS = [1e3, 2e3, 4e3];
|
|
16
|
+
var EventBuffer = (_class = class {
|
|
17
|
+
__init() {this._buffer = []}
|
|
18
|
+
__init2() {this._sessionCache = /* @__PURE__ */ new Map()}
|
|
19
|
+
__init3() {this._sessionCacheEventCount = 0}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
__init4() {this._timer = null}
|
|
23
|
+
__init5() {this._stopped = false}
|
|
24
|
+
/** Reference to the original (unpatched) https.request for anti-recursion. */
|
|
25
|
+
|
|
26
|
+
/** Reference to the original (unpatched) http.request for anti-recursion. */
|
|
27
|
+
|
|
28
|
+
constructor(apiKey, endpoint = _chunkOREJ23CVcjs.DEFAULT_ENDPOINT, autoFlush = true, originalHttpsRequest, originalHttpRequest) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);
|
|
29
|
+
this._apiKey = apiKey;
|
|
30
|
+
this._endpoint = endpoint.replace(/\/+$/, "");
|
|
31
|
+
this._originalHttpsRequest = _nullishCoalesce(originalHttpsRequest, () => ( _https2.default.request));
|
|
32
|
+
this._originalHttpRequest = _nullishCoalesce(originalHttpRequest, () => ( _http2.default.request));
|
|
33
|
+
if (autoFlush) {
|
|
34
|
+
this._timer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
|
|
35
|
+
this._timer.unref();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Add an event to the ring buffer. */
|
|
39
|
+
push(event) {
|
|
40
|
+
if (this._buffer.length >= MAX_LEN) {
|
|
41
|
+
this._buffer.shift();
|
|
42
|
+
}
|
|
43
|
+
this._buffer.push(event);
|
|
44
|
+
const sessionId = event.framework_session_id;
|
|
45
|
+
if (typeof sessionId === "string" && sessionId) {
|
|
46
|
+
if (this._sessionCacheEventCount < MAX_SESSION_CACHE_EVENTS) {
|
|
47
|
+
let sessionEvents = this._sessionCache.get(sessionId);
|
|
48
|
+
if (!sessionEvents) {
|
|
49
|
+
sessionEvents = [];
|
|
50
|
+
this._sessionCache.set(sessionId, sessionEvents);
|
|
51
|
+
}
|
|
52
|
+
if (sessionEvents.length < MAX_EVENTS_PER_SESSION) {
|
|
53
|
+
sessionEvents.push(event);
|
|
54
|
+
this._sessionCacheEventCount++;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Return a copy of events for the given session from the cache. */
|
|
60
|
+
getSessionEvents(sessionId) {
|
|
61
|
+
return [..._nullishCoalesce(this._sessionCache.get(sessionId), () => ( []))];
|
|
62
|
+
}
|
|
63
|
+
/** Remove session events from the cache. */
|
|
64
|
+
clearSession(sessionId) {
|
|
65
|
+
const events = this._sessionCache.get(sessionId);
|
|
66
|
+
if (events) {
|
|
67
|
+
this._sessionCacheEventCount = Math.max(
|
|
68
|
+
0,
|
|
69
|
+
this._sessionCacheEventCount - events.length
|
|
70
|
+
);
|
|
71
|
+
this._sessionCache.delete(sessionId);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Remove and return up to `count` events from the buffer. */
|
|
75
|
+
drain(count) {
|
|
76
|
+
const n = Math.min(count, this._buffer.length);
|
|
77
|
+
return this._buffer.splice(0, n);
|
|
78
|
+
}
|
|
79
|
+
/** Get all buffered events (without draining). For reporter access. */
|
|
80
|
+
peek() {
|
|
81
|
+
return [...this._buffer];
|
|
82
|
+
}
|
|
83
|
+
/** Number of buffered events. */
|
|
84
|
+
get length() {
|
|
85
|
+
return this._buffer.length;
|
|
86
|
+
}
|
|
87
|
+
/** Drain a batch and send to backend. */
|
|
88
|
+
async flush() {
|
|
89
|
+
const events = this.drain(BATCH_SIZE);
|
|
90
|
+
if (events.length === 0) return;
|
|
91
|
+
for (let attempt = 0; attempt < RETRY_ATTEMPTS; attempt++) {
|
|
92
|
+
try {
|
|
93
|
+
await this._send(events);
|
|
94
|
+
return;
|
|
95
|
+
} catch (e) {
|
|
96
|
+
if (attempt < RETRY_ATTEMPTS - 1) {
|
|
97
|
+
await sleep(RETRY_BACKOFF_MS[attempt]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** Stop flush timer and drain all remaining events. */
|
|
103
|
+
async shutdown() {
|
|
104
|
+
this._stopped = true;
|
|
105
|
+
if (this._timer) {
|
|
106
|
+
clearInterval(this._timer);
|
|
107
|
+
this._timer = null;
|
|
108
|
+
}
|
|
109
|
+
while (this._buffer.length > 0) {
|
|
110
|
+
const events = this.drain(BATCH_SIZE);
|
|
111
|
+
if (events.length === 0) break;
|
|
112
|
+
for (let attempt = 0; attempt < RETRY_ATTEMPTS; attempt++) {
|
|
113
|
+
try {
|
|
114
|
+
await this._send(events);
|
|
115
|
+
break;
|
|
116
|
+
} catch (e2) {
|
|
117
|
+
if (attempt === RETRY_ATTEMPTS - 1) {
|
|
118
|
+
} else {
|
|
119
|
+
await sleep(RETRY_BACKOFF_MS[attempt]);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/** POST events to the backend with gzip compression. */
|
|
126
|
+
_send(events) {
|
|
127
|
+
const payload = JSON.stringify({
|
|
128
|
+
events,
|
|
129
|
+
sdk_version: "0.1.0"
|
|
130
|
+
});
|
|
131
|
+
const compressed = _zlib.gzipSync.call(void 0, Buffer.from(payload, "utf-8"));
|
|
132
|
+
const url = new URL(`${this._endpoint}/v1/events`);
|
|
133
|
+
const isHttps = url.protocol === "https:";
|
|
134
|
+
const requestFn = isHttps ? this._originalHttpsRequest : this._originalHttpRequest;
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
const req = requestFn(
|
|
137
|
+
{
|
|
138
|
+
hostname: url.hostname,
|
|
139
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
140
|
+
path: url.pathname,
|
|
141
|
+
method: "POST",
|
|
142
|
+
headers: {
|
|
143
|
+
"Content-Type": "application/json",
|
|
144
|
+
"Content-Encoding": "gzip",
|
|
145
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
146
|
+
"Content-Length": compressed.length
|
|
147
|
+
},
|
|
148
|
+
timeout: 5e3
|
|
149
|
+
},
|
|
150
|
+
(res) => {
|
|
151
|
+
res.resume();
|
|
152
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
153
|
+
resolve();
|
|
154
|
+
} else {
|
|
155
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
req.on("error", reject);
|
|
160
|
+
req.on("timeout", () => {
|
|
161
|
+
req.destroy();
|
|
162
|
+
reject(new Error("Request timeout"));
|
|
163
|
+
});
|
|
164
|
+
req.write(compressed);
|
|
165
|
+
req.end();
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}, _class);
|
|
169
|
+
function sleep(ms) {
|
|
170
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/context.ts
|
|
174
|
+
var _async_hooks = require('async_hooks');
|
|
175
|
+
var _crypto = require('crypto');
|
|
176
|
+
|
|
177
|
+
// src/agent-detect.ts
|
|
178
|
+
var AGENT_ENV_MAP = [
|
|
179
|
+
{ envKey: "ARDEA_AGENT_NAME", name: "", useValue: true },
|
|
180
|
+
{ envKey: "CANARY_AGENT_NAME", name: "", useValue: true },
|
|
181
|
+
{ envKey: "CLAUDE_CODE", name: "claude_code" },
|
|
182
|
+
{ envKey: "CURSOR_TRACE_ID", name: "cursor" },
|
|
183
|
+
{ envKey: "WINDSURF_SESSION_ID", name: "windsurf" },
|
|
184
|
+
{ envKey: "CLINE_TASK_ID", name: "cline" },
|
|
185
|
+
{ envKey: "AIDER_VERSION", name: "aider", versionKey: "AIDER_VERSION" },
|
|
186
|
+
{ envKey: "DEVIN_SESSION_ID", name: "devin" },
|
|
187
|
+
{ envKey: "CODEX_CLI", name: "codex" },
|
|
188
|
+
{ envKey: "GITHUB_COPILOT", name: "github_copilot" },
|
|
189
|
+
{ envKey: "CODY_SESSION_ID", name: "sourcegraph_cody" }
|
|
190
|
+
];
|
|
191
|
+
var cachedInfo = null;
|
|
192
|
+
function detectAgent() {
|
|
193
|
+
if (cachedInfo) return cachedInfo;
|
|
194
|
+
for (const entry of AGENT_ENV_MAP) {
|
|
195
|
+
const val = process.env[entry.envKey];
|
|
196
|
+
if (val !== void 0) {
|
|
197
|
+
cachedInfo = {
|
|
198
|
+
agent_name: entry.useValue ? val || null : entry.name,
|
|
199
|
+
agent_version: entry.versionKey ? _nullishCoalesce(process.env[entry.versionKey], () => ( null)) : null
|
|
200
|
+
};
|
|
201
|
+
if (cachedInfo.agent_name) return cachedInfo;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
cachedInfo = { agent_name: null, agent_version: null };
|
|
205
|
+
return cachedInfo;
|
|
206
|
+
}
|
|
207
|
+
function resetAgentCache() {
|
|
208
|
+
cachedInfo = null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/context.ts
|
|
212
|
+
var sessionStorage = new (0, _async_hooks.AsyncLocalStorage)();
|
|
213
|
+
var sdkInstanceId = _crypto.randomUUID.call(void 0, ).replace(/-/g, "");
|
|
214
|
+
var callSequence = 0;
|
|
215
|
+
var defaultContext = {
|
|
216
|
+
frameworkSessionId: void 0
|
|
217
|
+
};
|
|
218
|
+
function getMutableContext() {
|
|
219
|
+
return _nullishCoalesce(sessionStorage.getStore(), () => ( defaultContext));
|
|
220
|
+
}
|
|
221
|
+
function nextCallSequence() {
|
|
222
|
+
callSequence += 1;
|
|
223
|
+
return callSequence;
|
|
224
|
+
}
|
|
225
|
+
function getBaseSignals() {
|
|
226
|
+
const ctx = getMutableContext();
|
|
227
|
+
const configuredParentProcessId = Number.parseInt(
|
|
228
|
+
process.env.ARDEA_PARENT_PROCESS_ID || process.env.CANARY_PARENT_PROCESS_ID || "",
|
|
229
|
+
10
|
|
230
|
+
);
|
|
231
|
+
const configuredProcessKind = process.env.ARDEA_PROCESS_KIND || process.env.CANARY_PROCESS_KIND || "agent";
|
|
232
|
+
const configuredProcessLabel = process.env.ARDEA_PROCESS_LABEL || process.env.CANARY_PROCESS_LABEL || void 0;
|
|
233
|
+
const signals = {
|
|
234
|
+
sdk_instance_id: sdkInstanceId,
|
|
235
|
+
process_id: process.pid,
|
|
236
|
+
thread_id: 0
|
|
237
|
+
};
|
|
238
|
+
if (Number.isFinite(configuredParentProcessId)) {
|
|
239
|
+
signals.parent_process_id = configuredParentProcessId;
|
|
240
|
+
}
|
|
241
|
+
if (configuredProcessKind) {
|
|
242
|
+
signals.process_kind = configuredProcessKind;
|
|
243
|
+
}
|
|
244
|
+
if (configuredProcessLabel) {
|
|
245
|
+
signals.process_label = configuredProcessLabel;
|
|
246
|
+
}
|
|
247
|
+
if (ctx.frameworkSessionId) {
|
|
248
|
+
signals.framework_session_id = ctx.frameworkSessionId;
|
|
249
|
+
}
|
|
250
|
+
const agentInfo = detectAgent();
|
|
251
|
+
if (agentInfo.agent_name) {
|
|
252
|
+
signals.agent_name = agentInfo.agent_name;
|
|
253
|
+
}
|
|
254
|
+
if (agentInfo.agent_version) {
|
|
255
|
+
signals.agent_version = agentInfo.agent_version;
|
|
256
|
+
}
|
|
257
|
+
return signals;
|
|
258
|
+
}
|
|
259
|
+
function getSessionSignals() {
|
|
260
|
+
return {
|
|
261
|
+
...getBaseSignals(),
|
|
262
|
+
call_sequence: nextCallSequence()
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
function setFrameworkSession(sessionId) {
|
|
266
|
+
getMutableContext().frameworkSessionId = sessionId;
|
|
267
|
+
}
|
|
268
|
+
function clearFrameworkSession() {
|
|
269
|
+
getMutableContext().frameworkSessionId = void 0;
|
|
270
|
+
}
|
|
271
|
+
function runWithSession(sessionId, fn) {
|
|
272
|
+
return sessionStorage.run(
|
|
273
|
+
{
|
|
274
|
+
frameworkSessionId: sessionId
|
|
275
|
+
},
|
|
276
|
+
fn
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/voucher.ts
|
|
281
|
+
var _fs = require('fs');
|
|
282
|
+
var _path = require('path');
|
|
283
|
+
var _os = require('os');
|
|
284
|
+
function getRedeemedPath() {
|
|
285
|
+
const dir = _path.join.call(void 0, _os.homedir.call(void 0, ), ".ardea");
|
|
286
|
+
_fs.mkdirSync.call(void 0, dir, { recursive: true });
|
|
287
|
+
return _path.join.call(void 0, dir, "redeemed-vouchers.json");
|
|
288
|
+
}
|
|
289
|
+
function getRedeemedSet() {
|
|
290
|
+
try {
|
|
291
|
+
const data = JSON.parse(_fs.readFileSync.call(void 0, getRedeemedPath(), "utf8"));
|
|
292
|
+
return new Set(Array.isArray(data) ? data : []);
|
|
293
|
+
} catch (e3) {
|
|
294
|
+
return /* @__PURE__ */ new Set();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function markRedeemed(code) {
|
|
298
|
+
const set = getRedeemedSet();
|
|
299
|
+
set.add(code);
|
|
300
|
+
_fs.writeFileSync.call(void 0, getRedeemedPath(), JSON.stringify([...set]));
|
|
301
|
+
}
|
|
302
|
+
function autoRedeemVoucher(apiKey, endpoint, code) {
|
|
303
|
+
if (!code.startsWith("CVR-")) return;
|
|
304
|
+
const redeemed = getRedeemedSet();
|
|
305
|
+
if (redeemed.has(code)) return;
|
|
306
|
+
fetch(`${endpoint}/v1/vouchers/redeem`, {
|
|
307
|
+
method: "POST",
|
|
308
|
+
headers: {
|
|
309
|
+
"Content-Type": "application/json",
|
|
310
|
+
Authorization: `Bearer ${apiKey}`
|
|
311
|
+
},
|
|
312
|
+
body: JSON.stringify({ code })
|
|
313
|
+
}).then(async (res) => {
|
|
314
|
+
if (res.ok) {
|
|
315
|
+
const data = await res.json();
|
|
316
|
+
markRedeemed(code);
|
|
317
|
+
console.error(
|
|
318
|
+
`[ardea] Voucher redeemed: +${_nullishCoalesce(data.credits, () => ( 0))} ${_nullishCoalesce(data.vendor, () => ( "unknown"))} credits`
|
|
319
|
+
);
|
|
320
|
+
} else if (res.status === 410) {
|
|
321
|
+
markRedeemed(code);
|
|
322
|
+
}
|
|
323
|
+
}).catch(() => {
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// src/index.ts
|
|
328
|
+
var _initialized = false;
|
|
329
|
+
var _buffer = null;
|
|
330
|
+
function init(config) {
|
|
331
|
+
if (_initialized) return;
|
|
332
|
+
if (!config.apiKey.startsWith("cnry_sk_")) {
|
|
333
|
+
throw new Error(
|
|
334
|
+
"Invalid API key: must start with 'cnry_sk_'. Get your key at https://app.canary.dev"
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
const endpoint = _nullishCoalesce(config.endpoint, () => ( _chunkOREJ23CVcjs.DEFAULT_ENDPOINT));
|
|
338
|
+
const autoFlush = _nullishCoalesce(config.autoFlush, () => ( true));
|
|
339
|
+
_buffer = new EventBuffer(config.apiKey, endpoint, autoFlush);
|
|
340
|
+
_initialized = true;
|
|
341
|
+
const voucherCode = process.env.ARDEA_VOUCHER || process.env.CANARY_VOUCHER;
|
|
342
|
+
if (voucherCode) {
|
|
343
|
+
autoRedeemVoucher(config.apiKey, endpoint, voucherCode);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
async function shutdown() {
|
|
347
|
+
if (!_initialized || !_buffer) return;
|
|
348
|
+
await _buffer.shutdown();
|
|
349
|
+
_buffer = null;
|
|
350
|
+
_initialized = false;
|
|
351
|
+
}
|
|
352
|
+
function survey(options) {
|
|
353
|
+
if (!_buffer) {
|
|
354
|
+
throw new Error("Canary SDK not initialized. Call init() first.");
|
|
355
|
+
}
|
|
356
|
+
const signals = getSessionSignals();
|
|
357
|
+
const event = {
|
|
358
|
+
event_type: "feedback",
|
|
359
|
+
source_plane: "declared",
|
|
360
|
+
capture_mode: "declared",
|
|
361
|
+
source: "manual",
|
|
362
|
+
worked: _nullishCoalesce(options.worked, () => ( true)),
|
|
363
|
+
context: _nullishCoalesce(options.context, () => ( "")),
|
|
364
|
+
ts: Date.now() / 1e3,
|
|
365
|
+
agent_name: _nullishCoalesce(signals.agent_name, () => ( void 0)),
|
|
366
|
+
agent_version: _nullishCoalesce(signals.agent_version, () => ( void 0)),
|
|
367
|
+
framework_session_id: _nullishCoalesce(_nullishCoalesce(options.sessionId, () => ( signals.framework_session_id)), () => ( void 0))
|
|
368
|
+
};
|
|
369
|
+
if (options.frictionPoints && options.frictionPoints.length > 0) {
|
|
370
|
+
event.friction_points = options.frictionPoints;
|
|
371
|
+
}
|
|
372
|
+
if (options.provider) {
|
|
373
|
+
event.provider = options.provider;
|
|
374
|
+
}
|
|
375
|
+
_buffer.push(event);
|
|
376
|
+
}
|
|
377
|
+
function getBuffer() {
|
|
378
|
+
return _buffer;
|
|
379
|
+
}
|
|
380
|
+
var Ardea = { init, shutdown, survey, getBuffer };
|
|
381
|
+
var Canary = Ardea;
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
exports.Ardea = Ardea; exports.Canary = Canary; exports.clearFrameworkSession = clearFrameworkSession; exports.detectAgent = detectAgent; exports.getBuffer = getBuffer; exports.getSessionSignals = getSessionSignals; exports.init = init; exports.resetAgentCache = resetAgentCache; exports.runWithSession = runWithSession; exports.setFrameworkSession = setFrameworkSession; exports.shutdown = shutdown; exports.survey = survey;
|
|
396
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/stalapaneni/conductor/workspaces/canary/bucharest/canary/dist/index.cjs","../src/buffer.ts","../src/context.ts","../src/agent-detect.ts","../src/voucher.ts","../src/index.ts"],"names":[],"mappings":"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","file":"/Users/stalapaneni/conductor/workspaces/canary/bucharest/canary/dist/index.cjs","sourcesContent":[null,"/**\n * Event buffer with ring buffer, gzip flush, retry, and session cache.\n *\n * Mirrors sdk/src/ardea/buffer.py.\n */\n\nimport { gzipSync } from \"node:zlib\";\nimport https from \"node:https\";\nimport http from \"node:http\";\nimport type { CanaryEvent } from \"./types.js\";\nimport { DEFAULT_ENDPOINT } from \"./constants.js\";\n\nconst MAX_LEN = 10_000;\nconst BATCH_SIZE = 100;\nconst FLUSH_INTERVAL_MS = 10_000;\nconst MAX_EVENTS_PER_SESSION = 500;\nconst MAX_SESSION_CACHE_EVENTS = 5_000;\nconst RETRY_ATTEMPTS = 3;\nconst RETRY_BACKOFF_MS = [1000, 2000, 4000];\n\nexport class EventBuffer {\n private _buffer: CanaryEvent[] = [];\n private _sessionCache: Map<string, CanaryEvent[]> = new Map();\n private _sessionCacheEventCount = 0;\n private _apiKey: string;\n private _endpoint: string;\n private _timer: ReturnType<typeof setInterval> | null = null;\n private _stopped = false;\n /** Reference to the original (unpatched) https.request for anti-recursion. */\n private _originalHttpsRequest: typeof https.request;\n /** Reference to the original (unpatched) http.request for anti-recursion. */\n private _originalHttpRequest: typeof http.request;\n\n constructor(\n apiKey: string,\n endpoint = DEFAULT_ENDPOINT,\n autoFlush = true,\n originalHttpsRequest?: typeof https.request,\n originalHttpRequest?: typeof http.request\n ) {\n this._apiKey = apiKey;\n this._endpoint = endpoint.replace(/\\/+$/, \"\");\n this._originalHttpsRequest = originalHttpsRequest ?? https.request;\n this._originalHttpRequest = originalHttpRequest ?? http.request;\n\n if (autoFlush) {\n this._timer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);\n this._timer.unref();\n }\n }\n\n /** Add an event to the ring buffer. */\n push(event: CanaryEvent): void {\n if (this._buffer.length >= MAX_LEN) {\n this._buffer.shift(); // drop oldest\n }\n this._buffer.push(event);\n\n // Retain in session cache\n const sessionId = (event as unknown as Record<string, unknown>).framework_session_id;\n if (typeof sessionId === \"string\" && sessionId) {\n if (this._sessionCacheEventCount < MAX_SESSION_CACHE_EVENTS) {\n let sessionEvents = this._sessionCache.get(sessionId);\n if (!sessionEvents) {\n sessionEvents = [];\n this._sessionCache.set(sessionId, sessionEvents);\n }\n if (sessionEvents.length < MAX_EVENTS_PER_SESSION) {\n sessionEvents.push(event);\n this._sessionCacheEventCount++;\n }\n }\n }\n }\n\n /** Return a copy of events for the given session from the cache. */\n getSessionEvents(sessionId: string): CanaryEvent[] {\n return [...(this._sessionCache.get(sessionId) ?? [])];\n }\n\n /** Remove session events from the cache. */\n clearSession(sessionId: string): void {\n const events = this._sessionCache.get(sessionId);\n if (events) {\n this._sessionCacheEventCount = Math.max(\n 0,\n this._sessionCacheEventCount - events.length\n );\n this._sessionCache.delete(sessionId);\n }\n }\n\n /** Remove and return up to `count` events from the buffer. */\n drain(count: number): CanaryEvent[] {\n const n = Math.min(count, this._buffer.length);\n return this._buffer.splice(0, n);\n }\n\n /** Get all buffered events (without draining). For reporter access. */\n peek(): CanaryEvent[] {\n return [...this._buffer];\n }\n\n /** Number of buffered events. */\n get length(): number {\n return this._buffer.length;\n }\n\n /** Drain a batch and send to backend. */\n async flush(): Promise<void> {\n const events = this.drain(BATCH_SIZE);\n if (events.length === 0) return;\n\n for (let attempt = 0; attempt < RETRY_ATTEMPTS; attempt++) {\n try {\n await this._send(events);\n return;\n } catch {\n if (attempt < RETRY_ATTEMPTS - 1) {\n await sleep(RETRY_BACKOFF_MS[attempt]);\n }\n }\n }\n // All retries failed — events are lost (no disk persistence in Node SDK v1)\n }\n\n /** Stop flush timer and drain all remaining events. */\n async shutdown(): Promise<void> {\n this._stopped = true;\n if (this._timer) {\n clearInterval(this._timer);\n this._timer = null;\n }\n\n while (this._buffer.length > 0) {\n const events = this.drain(BATCH_SIZE);\n if (events.length === 0) break;\n\n for (let attempt = 0; attempt < RETRY_ATTEMPTS; attempt++) {\n try {\n await this._send(events);\n break;\n } catch {\n if (attempt === RETRY_ATTEMPTS - 1) {\n // Final failure — events lost\n } else {\n await sleep(RETRY_BACKOFF_MS[attempt]);\n }\n }\n }\n }\n }\n\n /** POST events to the backend with gzip compression. */\n private _send(events: CanaryEvent[]): Promise<void> {\n const payload = JSON.stringify({\n events,\n sdk_version: \"0.1.0\",\n });\n const compressed = gzipSync(Buffer.from(payload, \"utf-8\"));\n\n const url = new URL(`${this._endpoint}/v1/events`);\n const isHttps = url.protocol === \"https:\";\n const requestFn = isHttps ? this._originalHttpsRequest : this._originalHttpRequest;\n\n return new Promise<void>((resolve, reject) => {\n const req = requestFn(\n {\n hostname: url.hostname,\n port: url.port || (isHttps ? 443 : 80),\n path: url.pathname,\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Encoding\": \"gzip\",\n Authorization: `Bearer ${this._apiKey}`,\n \"Content-Length\": compressed.length,\n },\n timeout: 5000,\n },\n (res) => {\n // Consume the response\n res.resume();\n if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {\n resolve();\n } else {\n reject(new Error(`HTTP ${res.statusCode}`));\n }\n }\n );\n\n req.on(\"error\", reject);\n req.on(\"timeout\", () => {\n req.destroy();\n reject(new Error(\"Request timeout\"));\n });\n req.write(compressed);\n req.end();\n });\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * Session correlation via AsyncLocalStorage.\n *\n * Node.js equivalent of sdk/src/canary/context.py using\n * AsyncLocalStorage instead of ContextVar.\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { randomUUID } from \"node:crypto\";\nimport { detectAgent } from \"./agent-detect.js\";\n\ninterface SessionContext {\n frameworkSessionId?: string;\n}\n\nconst sessionStorage = new AsyncLocalStorage<SessionContext>();\nconst sdkInstanceId = randomUUID().replace(/-/g, \"\");\nlet callSequence = 0;\nconst defaultContext: SessionContext = {\n frameworkSessionId: undefined,\n};\n\nfunction getMutableContext(): SessionContext {\n return sessionStorage.getStore() ?? defaultContext;\n}\n\nfunction nextCallSequence(): number {\n callSequence += 1;\n return callSequence;\n}\n\nexport interface SessionSignals {\n sdk_instance_id: string;\n process_id: number;\n parent_process_id?: number;\n process_kind?: string;\n process_label?: string;\n thread_id: number;\n call_sequence: number;\n framework_session_id?: string;\n agent_name?: string;\n agent_version?: string;\n}\n\nfunction getBaseSignals(): Omit<SessionSignals, \"call_sequence\"> {\n const ctx = getMutableContext();\n const configuredParentProcessId = Number.parseInt(\n process.env.ARDEA_PARENT_PROCESS_ID || process.env.CANARY_PARENT_PROCESS_ID || \"\",\n 10\n );\n const configuredProcessKind = process.env.ARDEA_PROCESS_KIND || process.env.CANARY_PROCESS_KIND || \"agent\";\n const configuredProcessLabel = process.env.ARDEA_PROCESS_LABEL || process.env.CANARY_PROCESS_LABEL || undefined;\n const signals: Omit<SessionSignals, \"call_sequence\"> = {\n sdk_instance_id: sdkInstanceId,\n process_id: process.pid,\n thread_id: 0,\n };\n\n if (Number.isFinite(configuredParentProcessId)) {\n signals.parent_process_id = configuredParentProcessId;\n }\n if (configuredProcessKind) {\n signals.process_kind = configuredProcessKind;\n }\n if (configuredProcessLabel) {\n signals.process_label = configuredProcessLabel;\n }\n\n if (ctx.frameworkSessionId) {\n signals.framework_session_id = ctx.frameworkSessionId;\n }\n\n const agentInfo = detectAgent();\n if (agentInfo.agent_name) {\n signals.agent_name = agentInfo.agent_name;\n }\n if (agentInfo.agent_version) {\n signals.agent_version = agentInfo.agent_version;\n }\n\n return signals;\n}\n\n/**\n * Return session correlation signals for the current execution context.\n */\nexport function getSessionSignals(): SessionSignals {\n return {\n ...getBaseSignals(),\n call_sequence: nextCallSequence(),\n };\n}\n\n/**\n * Set the framework session ID for the current async context.\n *\n * If no async context is active, this becomes the default session ID used\n * by subsequent feedback/reporting calls on this process.\n */\nexport function setFrameworkSession(sessionId: string): void {\n getMutableContext().frameworkSessionId = sessionId;\n}\n\n/**\n * Clear the framework session ID for the current async context.\n */\nexport function clearFrameworkSession(): void {\n getMutableContext().frameworkSessionId = undefined;\n}\n\n/**\n * Run a function with a scoped framework session ID.\n *\n * The session ID propagates through async call chains automatically.\n */\nexport function runWithSession<T>(sessionId: string, fn: () => T): T {\n return sessionStorage.run(\n {\n frameworkSessionId: sessionId,\n },\n fn\n );\n}\n\n/**\n * Reset all context state. For testing only.\n */\nexport function resetContext(): void {\n callSequence = 0;\n defaultContext.frameworkSessionId = undefined;\n}\n","/**\n * Detect which AI agent framework is running via environment variables.\n */\n\nexport interface AgentInfo {\n agent_name: string | null;\n agent_version: string | null;\n}\n\nexport const AGENT_ENV_MAP: ReadonlyArray<{ envKey: string; name: string; versionKey?: string; useValue?: boolean }> = [\n { envKey: \"ARDEA_AGENT_NAME\", name: \"\", useValue: true },\n { envKey: \"CANARY_AGENT_NAME\", name: \"\", useValue: true },\n { envKey: \"CLAUDE_CODE\", name: \"claude_code\" },\n { envKey: \"CURSOR_TRACE_ID\", name: \"cursor\" },\n { envKey: \"WINDSURF_SESSION_ID\", name: \"windsurf\" },\n { envKey: \"CLINE_TASK_ID\", name: \"cline\" },\n { envKey: \"AIDER_VERSION\", name: \"aider\", versionKey: \"AIDER_VERSION\" },\n { envKey: \"DEVIN_SESSION_ID\", name: \"devin\" },\n { envKey: \"CODEX_CLI\", name: \"codex\" },\n { envKey: \"GITHUB_COPILOT\", name: \"github_copilot\" },\n { envKey: \"CODY_SESSION_ID\", name: \"sourcegraph_cody\" },\n];\n\nlet cachedInfo: AgentInfo | null = null;\n\nexport function detectAgent(): AgentInfo {\n if (cachedInfo) return cachedInfo;\n\n for (const entry of AGENT_ENV_MAP) {\n const val = process.env[entry.envKey];\n // Check !== undefined so empty strings (presence flags) still detect\n if (val !== undefined) {\n cachedInfo = {\n agent_name: entry.useValue ? (val || null) : entry.name,\n agent_version: entry.versionKey ? (process.env[entry.versionKey] ?? null) : null,\n };\n if (cachedInfo.agent_name) return cachedInfo;\n }\n }\n\n cachedInfo = { agent_name: null, agent_version: null };\n return cachedInfo;\n}\n\nexport function resetAgentCache(): void {\n cachedInfo = null;\n}\n","/**\n * Auto-redeem a voucher code on SDK init.\n * Non-blocking — fires in the background, never delays SDK startup.\n * Tracks redeemed codes in ~/.ardea/redeemed-vouchers.json.\n */\n\nimport { readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\ntype VoucherRedeemResponse = {\n credits?: number;\n vendor?: string;\n};\n\nfunction getRedeemedPath(): string {\n const dir = join(homedir(), \".ardea\");\n mkdirSync(dir, { recursive: true });\n return join(dir, \"redeemed-vouchers.json\");\n}\n\nfunction getRedeemedSet(): Set<string> {\n try {\n const data = JSON.parse(readFileSync(getRedeemedPath(), \"utf8\"));\n return new Set(Array.isArray(data) ? data : []);\n } catch {\n return new Set();\n }\n}\n\nfunction markRedeemed(code: string): void {\n const set = getRedeemedSet();\n set.add(code);\n writeFileSync(getRedeemedPath(), JSON.stringify([...set]));\n}\n\nexport function autoRedeemVoucher(\n apiKey: string,\n endpoint: string,\n code: string,\n): void {\n if (!code.startsWith(\"CVR-\")) return;\n\n const redeemed = getRedeemedSet();\n if (redeemed.has(code)) return;\n\n // Fire-and-forget — don't block SDK init\n fetch(`${endpoint}/v1/vouchers/redeem`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify({ code }),\n })\n .then(async (res) => {\n if (res.ok) {\n const data = (await res.json()) as VoucherRedeemResponse;\n markRedeemed(code);\n // Log to stderr to avoid corrupting any MCP stdio\n console.error(\n `[ardea] Voucher redeemed: +${data.credits ?? 0} ${data.vendor ?? \"unknown\"} credits`,\n );\n } else if (res.status === 410) {\n // Already redeemed, expired, or revoked — mark locally to avoid retries\n markRedeemed(code);\n }\n })\n .catch(() => {\n // Silently fail — SDK init must not be affected\n });\n}\n","/**\n * ardea — MCP tools for AI agents to report API experiences.\n */\n\nimport { EventBuffer } from \"./buffer.js\";\nimport {\n runWithSession,\n setFrameworkSession,\n clearFrameworkSession,\n getSessionSignals,\n} from \"./context.js\";\nimport type { CanaryConfig, CanaryEvent, FeedbackEvent } from \"./types.js\";\nimport { DEFAULT_ENDPOINT } from \"./constants.js\";\nimport { autoRedeemVoucher } from \"./voucher.js\";\n\nlet _initialized = false;\nlet _buffer: EventBuffer | null = null;\n\n/**\n * Initialize Canary SDK for manual feedback submission.\n */\nexport function init(config: CanaryConfig): void {\n if (_initialized) return;\n\n if (!config.apiKey.startsWith(\"cnry_sk_\")) {\n throw new Error(\n \"Invalid API key: must start with 'cnry_sk_'. \" +\n \"Get your key at https://app.canary.dev\"\n );\n }\n\n const endpoint = config.endpoint ?? DEFAULT_ENDPOINT;\n const autoFlush = config.autoFlush ?? true;\n\n _buffer = new EventBuffer(config.apiKey, endpoint, autoFlush);\n _initialized = true;\n\n // Auto-redeem voucher if ARDEA_VOUCHER or CANARY_VOUCHER env var is set (non-blocking)\n const voucherCode = process.env.ARDEA_VOUCHER || process.env.CANARY_VOUCHER;\n if (voucherCode) {\n autoRedeemVoucher(config.apiKey, endpoint, voucherCode);\n }\n}\n\n/**\n * Shut down the SDK and flush any queued feedback events.\n */\nexport async function shutdown(): Promise<void> {\n if (!_initialized || !_buffer) return;\n\n await _buffer.shutdown();\n _buffer = null;\n _initialized = false;\n}\n\n/**\n * Submit manual feedback.\n */\nexport function survey(options: {\n worked?: boolean;\n context?: string;\n frictionPoints?: string[];\n provider?: string;\n sessionId?: string;\n}): void {\n if (!_buffer) {\n throw new Error(\"Canary SDK not initialized. Call init() first.\");\n }\n\n // Pick up session context from AsyncLocalStorage (set by runWithSession)\n const signals = getSessionSignals();\n const event: FeedbackEvent = {\n event_type: \"feedback\",\n source_plane: \"declared\",\n capture_mode: \"declared\",\n source: \"manual\",\n worked: options.worked ?? true,\n context: options.context ?? \"\",\n ts: Date.now() / 1000,\n agent_name: signals.agent_name ?? undefined,\n agent_version: signals.agent_version ?? undefined,\n framework_session_id: options.sessionId ?? signals.framework_session_id ?? undefined,\n };\n\n if (options.frictionPoints && options.frictionPoints.length > 0) {\n event.friction_points = options.frictionPoints;\n }\n if (options.provider) {\n event.provider = options.provider;\n }\n\n _buffer.push(event);\n}\n\n/**\n * Get the global EventBuffer (for advanced usage / adapters).\n */\nexport function getBuffer(): EventBuffer | null {\n return _buffer;\n}\n\n// Re-export session helpers\nexport {\n runWithSession,\n setFrameworkSession,\n clearFrameworkSession,\n getSessionSignals,\n};\n\n// Re-export types\nexport type { ArdeaConfig, CanaryConfig, CanaryEvent, FeedbackEvent } from \"./types.js\";\nexport { detectAgent, resetAgentCache } from \"./agent-detect.js\";\n\n/** Alias for init — Ardea rebrand entry point. */\nexport const Ardea = { init, shutdown, survey, getBuffer };\n\n/** @deprecated Use Ardea instead. */\nexport const Canary = Ardea;\n"]}
|