@agentuity/runtime 0.0.71 → 0.0.73
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/dist/_process-protection.d.ts +25 -0
- package/dist/_process-protection.d.ts.map +1 -0
- package/dist/_process-protection.js +51 -0
- package/dist/_process-protection.js.map +1 -0
- package/dist/_server.d.ts.map +1 -1
- package/dist/_server.js +9 -17
- package/dist/_server.js.map +1 -1
- package/dist/_waituntil.d.ts.map +1 -1
- package/dist/_waituntil.js +9 -2
- package/dist/_waituntil.js.map +1 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +30 -5
- package/dist/agent.js.map +1 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +8 -1
- package/dist/app.js.map +1 -1
- package/dist/otel/console.d.ts +2 -0
- package/dist/otel/console.d.ts.map +1 -1
- package/dist/otel/console.js +34 -18
- package/dist/otel/console.js.map +1 -1
- package/dist/otel/otel.d.ts.map +1 -1
- package/dist/otel/otel.js +6 -3
- package/dist/otel/otel.js.map +1 -1
- package/dist/services/evalrun/http.d.ts.map +1 -1
- package/dist/services/evalrun/http.js +2 -2
- package/dist/services/evalrun/http.js.map +1 -1
- package/dist/services/local/keyvalue.d.ts.map +1 -1
- package/dist/services/local/keyvalue.js +5 -1
- package/dist/services/local/keyvalue.js.map +1 -1
- package/dist/services/session/http.d.ts.map +1 -1
- package/dist/services/session/http.js +2 -2
- package/dist/services/session/http.js.map +1 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +19 -4
- package/dist/session.js.map +1 -1
- package/dist/workbench.d.ts.map +1 -1
- package/dist/workbench.js +1 -21
- package/dist/workbench.js.map +1 -1
- package/package.json +5 -5
- package/src/_process-protection.ts +66 -0
- package/src/_server.ts +10 -17
- package/src/_waituntil.ts +11 -2
- package/src/agent.ts +35 -5
- package/src/app.ts +7 -1
- package/src/otel/console.ts +34 -18
- package/src/otel/otel.ts +6 -3
- package/src/services/evalrun/http.ts +4 -6
- package/src/services/local/keyvalue.ts +7 -1
- package/src/services/session/http.ts +4 -6
- package/src/session.ts +19 -4
- package/src/workbench.ts +1 -21
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process protection utilities
|
|
3
|
+
*
|
|
4
|
+
* Prevents user code from calling process.exit() which would crash the server.
|
|
5
|
+
* The runtime can still exit gracefully using the internal exit function.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { StructuredError } from '@agentuity/core';
|
|
9
|
+
|
|
10
|
+
// Store the original process.exit
|
|
11
|
+
const originalExit = process.exit.bind(process);
|
|
12
|
+
|
|
13
|
+
// Flag to track if protection is enabled
|
|
14
|
+
let protectionEnabled = false;
|
|
15
|
+
|
|
16
|
+
const ProcessExitAttemptError = StructuredError(
|
|
17
|
+
'ProcessExitAttemptError',
|
|
18
|
+
'Calling process.exit() is not allowed in agent code. The server must remain running to handle requests.'
|
|
19
|
+
)<{
|
|
20
|
+
code?: number | string | null | undefined;
|
|
21
|
+
}>();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Enable protection against process.exit calls.
|
|
25
|
+
* After calling this, user code calling process.exit() will throw an error.
|
|
26
|
+
*/
|
|
27
|
+
export function enableProcessExitProtection(): void {
|
|
28
|
+
if (protectionEnabled) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
protectionEnabled = true;
|
|
33
|
+
|
|
34
|
+
// Replace process.exit with a function that throws
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
+
(process as any).exit = function (code?: number | string | null | undefined): never {
|
|
37
|
+
throw new ProcessExitAttemptError({ code });
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Disable protection (mainly for testing)
|
|
43
|
+
*/
|
|
44
|
+
export function disableProcessExitProtection(): void {
|
|
45
|
+
if (!protectionEnabled) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
protectionEnabled = false;
|
|
50
|
+
process.exit = originalExit;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Internal function for the runtime to call when it needs to exit.
|
|
55
|
+
* This bypasses the protection and calls the original process.exit.
|
|
56
|
+
*/
|
|
57
|
+
export function internalExit(code?: number): never {
|
|
58
|
+
return originalExit(code);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check if protection is currently enabled
|
|
63
|
+
*/
|
|
64
|
+
export function isProtectionEnabled(): boolean {
|
|
65
|
+
return protectionEnabled;
|
|
66
|
+
}
|
package/src/_server.ts
CHANGED
|
@@ -26,8 +26,9 @@ import { register } from './otel/config';
|
|
|
26
26
|
import type { Logger } from './logger';
|
|
27
27
|
import { isIdle } from './_idle';
|
|
28
28
|
import * as runtimeConfig from './_config';
|
|
29
|
-
import {
|
|
29
|
+
import { runInHTTPContext } from './_context';
|
|
30
30
|
import { runAgentShutdowns, createAgentMiddleware } from './agent';
|
|
31
|
+
import { enableProcessExitProtection, internalExit } from './_process-protection';
|
|
31
32
|
import {
|
|
32
33
|
createServices,
|
|
33
34
|
getThreadProvider,
|
|
@@ -107,17 +108,6 @@ function registerAgentuitySpanProcessor() {
|
|
|
107
108
|
'@agentuity/devmode': devmode,
|
|
108
109
|
'@agentuity/environment': environment,
|
|
109
110
|
};
|
|
110
|
-
if (inAgentContext()) {
|
|
111
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
112
|
-
const { getCurrentAgentMetadata } = require('./_context');
|
|
113
|
-
const current = getCurrentAgentMetadata();
|
|
114
|
-
if (current) {
|
|
115
|
-
attrs['@agentuity/agentId'] = current.id;
|
|
116
|
-
attrs['@agentuity/agentInstanceId'] = current.agentId;
|
|
117
|
-
attrs['@agentuity/agentDescription'] = current.description;
|
|
118
|
-
attrs['@agentuity/agentName'] = current.name;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
111
|
span.setAttributes(attrs);
|
|
122
112
|
}
|
|
123
113
|
|
|
@@ -168,6 +158,9 @@ export const createServer = async <TAppState>(
|
|
|
168
158
|
const hostname = '127.0.0.1';
|
|
169
159
|
const serverUrl = `http://${hostname}:${port}`;
|
|
170
160
|
|
|
161
|
+
// Enable process.exit protection before any user code can run
|
|
162
|
+
enableProcessExitProtection();
|
|
163
|
+
|
|
171
164
|
// this must come before registering any otel stuff
|
|
172
165
|
registerAgentuitySpanProcessor();
|
|
173
166
|
registerTokenProcessor();
|
|
@@ -377,7 +370,7 @@ export const createServer = async <TAppState>(
|
|
|
377
370
|
// Force exit after timeout if cleanup hangs
|
|
378
371
|
const forceExitTimer = setTimeout(() => {
|
|
379
372
|
otel.logger.warn('shutdown timed out after 5s, forcing exit');
|
|
380
|
-
|
|
373
|
+
internalExit(1);
|
|
381
374
|
}, 5_000);
|
|
382
375
|
try {
|
|
383
376
|
// stop accepting new connections
|
|
@@ -424,21 +417,21 @@ export const createServer = async <TAppState>(
|
|
|
424
417
|
|
|
425
418
|
process.once('SIGINT', async () => {
|
|
426
419
|
await shutdown();
|
|
427
|
-
|
|
420
|
+
internalExit(0);
|
|
428
421
|
});
|
|
429
422
|
process.once('SIGTERM', async () => {
|
|
430
423
|
await shutdown();
|
|
431
|
-
|
|
424
|
+
internalExit(0);
|
|
432
425
|
});
|
|
433
426
|
process.once('uncaughtException', async (err) => {
|
|
434
427
|
otel.logger.error('An uncaught exception was received: %s', err);
|
|
435
428
|
await shutdown();
|
|
436
|
-
|
|
429
|
+
internalExit(1);
|
|
437
430
|
});
|
|
438
431
|
process.once('unhandledRejection', async (reason) => {
|
|
439
432
|
otel.logger.error('An unhandled promise rejection was received: %s', reason);
|
|
440
433
|
await shutdown();
|
|
441
|
-
|
|
434
|
+
internalExit(1);
|
|
442
435
|
});
|
|
443
436
|
|
|
444
437
|
const server = Bun.serve({
|
package/src/_waituntil.ts
CHANGED
|
@@ -61,7 +61,8 @@ export default class WaitUntilHandler {
|
|
|
61
61
|
} catch (ex: unknown) {
|
|
62
62
|
span.recordException(ex as Error);
|
|
63
63
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
64
|
-
throw
|
|
64
|
+
// Log the error but don't re-throw - background tasks should never crash the server
|
|
65
|
+
internal.error('Background task error: %s', ex);
|
|
65
66
|
} finally {
|
|
66
67
|
span.end();
|
|
67
68
|
}
|
|
@@ -93,8 +94,16 @@ export default class WaitUntilHandler {
|
|
|
93
94
|
internal.debug(`⏳ Waiting for ${this.promises.length} promises to complete...`);
|
|
94
95
|
try {
|
|
95
96
|
// Promises are already executing, just wait for them to complete
|
|
96
|
-
|
|
97
|
+
// Use allSettled so one failing promise doesn't stop others
|
|
98
|
+
const results = await Promise.allSettled(this.promises);
|
|
97
99
|
const duration = Date.now() - (this.started as number);
|
|
100
|
+
|
|
101
|
+
// Log any failures
|
|
102
|
+
const failures = results.filter((r) => r.status === 'rejected');
|
|
103
|
+
if (failures.length > 0) {
|
|
104
|
+
logger.error('%d background task(s) failed during execution', failures.length);
|
|
105
|
+
}
|
|
106
|
+
|
|
98
107
|
internal.debug(
|
|
99
108
|
'✅ All promises completed, marking session completed (duration %dms)',
|
|
100
109
|
duration
|
package/src/agent.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type VectorStorage,
|
|
8
8
|
type InferOutput,
|
|
9
9
|
toCamelCase,
|
|
10
|
+
EvalRunStartEvent,
|
|
10
11
|
} from '@agentuity/core';
|
|
11
12
|
import { context, SpanStatusCode, type Tracer, trace } from '@opentelemetry/api';
|
|
12
13
|
import type { Context, MiddlewareHandler } from 'hono';
|
|
@@ -15,6 +16,8 @@ import { validator } from 'hono/validator';
|
|
|
15
16
|
import { AGENT_RUNTIME, INTERNAL_AGENT, CURRENT_AGENT } from './_config';
|
|
16
17
|
import {
|
|
17
18
|
getAgentContext,
|
|
19
|
+
inHTTPContext,
|
|
20
|
+
getHTTPContext,
|
|
18
21
|
setupRequestAgentContext,
|
|
19
22
|
type RequestAgentContextArgs,
|
|
20
23
|
} from './_context';
|
|
@@ -27,7 +30,6 @@ import { privateContext, notifyReady } from './_server';
|
|
|
27
30
|
import { generateId } from './session';
|
|
28
31
|
import { getEvalRunEventProvider } from './_services';
|
|
29
32
|
import * as runtimeConfig from './_config';
|
|
30
|
-
import type { EvalRunStartEvent } from '@agentuity/core';
|
|
31
33
|
import type { AppState } from './index';
|
|
32
34
|
import { validateSchema, formatValidationIssues } from './_validation';
|
|
33
35
|
|
|
@@ -1198,10 +1200,15 @@ async function fireAgentEvent(
|
|
|
1198
1200
|
const callbacks = listeners.get(eventName);
|
|
1199
1201
|
if (callbacks && callbacks.size > 0) {
|
|
1200
1202
|
for (const callback of callbacks) {
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1203
|
+
try {
|
|
1204
|
+
if (eventName === 'errored' && data) {
|
|
1205
|
+
await (callback as any)(eventName, agent, context, data);
|
|
1206
|
+
} else if (eventName === 'started' || eventName === 'completed') {
|
|
1207
|
+
await (callback as any)(eventName, agent, context);
|
|
1208
|
+
}
|
|
1209
|
+
} catch (error) {
|
|
1210
|
+
// Log but don't re-throw - event listener errors should not crash the server
|
|
1211
|
+
internal.error(`Error in agent event listener for '${eventName}':`, error);
|
|
1205
1212
|
}
|
|
1206
1213
|
}
|
|
1207
1214
|
}
|
|
@@ -1516,6 +1523,29 @@ export function createAgent<
|
|
|
1516
1523
|
// Store current agent for telemetry (using Symbol to keep it internal)
|
|
1517
1524
|
(agentCtx as any)[CURRENT_AGENT] = agent;
|
|
1518
1525
|
|
|
1526
|
+
const attrs = {
|
|
1527
|
+
'@agentuity/agentId': agent.metadata.id,
|
|
1528
|
+
'@agentuity/agentInstanceId': agent.metadata.agentId,
|
|
1529
|
+
'@agentuity/agentDescription': agent.metadata.description,
|
|
1530
|
+
'@agentuity/agentName': agent.metadata.name,
|
|
1531
|
+
};
|
|
1532
|
+
|
|
1533
|
+
// Set agent attributes on the current active span
|
|
1534
|
+
const activeSpan = trace.getActiveSpan();
|
|
1535
|
+
if (activeSpan) {
|
|
1536
|
+
activeSpan.setAttributes(attrs);
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
if (inHTTPContext()) {
|
|
1540
|
+
const honoCtx = privateContext(getHTTPContext());
|
|
1541
|
+
if (honoCtx.var.agentIds) {
|
|
1542
|
+
honoCtx.var.agentIds.add(agent.metadata.id);
|
|
1543
|
+
honoCtx.var.agentIds.add(agent.metadata.agentId);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
agentCtx.logger = agentCtx.logger.child(attrs);
|
|
1548
|
+
|
|
1519
1549
|
// Get the agent instance from the runtime state to fire events
|
|
1520
1550
|
const runtime = getAgentRuntime(agentCtx);
|
|
1521
1551
|
|
package/src/app.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { BunWebSocketData } from 'hono/bun';
|
|
|
6
6
|
import type { Logger } from './logger';
|
|
7
7
|
import { createServer, getLogger } from './_server';
|
|
8
8
|
import type { Meter, Tracer } from '@opentelemetry/api';
|
|
9
|
+
import { internal } from './logger/internal';
|
|
9
10
|
import type {
|
|
10
11
|
KeyValueStorage,
|
|
11
12
|
SessionEventProvider,
|
|
@@ -279,7 +280,12 @@ export class App<TAppState = Record<string, never>> {
|
|
|
279
280
|
if (!callbacks || callbacks.size === 0) return;
|
|
280
281
|
|
|
281
282
|
for (const callback of callbacks) {
|
|
282
|
-
|
|
283
|
+
try {
|
|
284
|
+
await callback(eventName, ...args);
|
|
285
|
+
} catch (error) {
|
|
286
|
+
// Log but don't re-throw - event listener errors should not crash the server
|
|
287
|
+
internal.error(`Error in app event listener for '${eventName}':`, error);
|
|
288
|
+
}
|
|
283
289
|
}
|
|
284
290
|
}
|
|
285
291
|
}
|
package/src/otel/console.ts
CHANGED
|
@@ -9,6 +9,11 @@ import { __originalConsole } from './logger';
|
|
|
9
9
|
* Uses __originalConsole to avoid infinite loop when console is patched
|
|
10
10
|
*/
|
|
11
11
|
export class ConsoleLogRecordExporter implements LogRecordExporter {
|
|
12
|
+
private dumpRecords = false;
|
|
13
|
+
|
|
14
|
+
constructor(dumpRecords: boolean) {
|
|
15
|
+
this.dumpRecords = dumpRecords;
|
|
16
|
+
}
|
|
12
17
|
/**
|
|
13
18
|
* Exports log records to the console
|
|
14
19
|
*
|
|
@@ -17,24 +22,35 @@ export class ConsoleLogRecordExporter implements LogRecordExporter {
|
|
|
17
22
|
*/
|
|
18
23
|
export(logs: ReadableLogRecord[], resultCallback: (result: ExportResult) => void): void {
|
|
19
24
|
for (const log of logs) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
25
|
+
if (this.dumpRecords) {
|
|
26
|
+
__originalConsole.log('[LOG]', {
|
|
27
|
+
body: log.body,
|
|
28
|
+
severityNumber: log.severityNumber,
|
|
29
|
+
severityText: log.severityText,
|
|
30
|
+
timestamp: log.hrTime,
|
|
31
|
+
attributes: log.attributes,
|
|
32
|
+
resource: log.resource.attributes,
|
|
33
|
+
});
|
|
34
|
+
} else {
|
|
35
|
+
const severity = log.severityNumber ? SeverityNumber[log.severityNumber] : 'INFO';
|
|
36
|
+
const msg = `[${severity}] ${log.body}`;
|
|
37
|
+
switch (log.severityNumber) {
|
|
38
|
+
case SeverityNumber.DEBUG:
|
|
39
|
+
__originalConsole.debug(msg);
|
|
40
|
+
break;
|
|
41
|
+
case SeverityNumber.INFO:
|
|
42
|
+
__originalConsole.info(msg);
|
|
43
|
+
break;
|
|
44
|
+
case SeverityNumber.WARN:
|
|
45
|
+
__originalConsole.warn(msg);
|
|
46
|
+
break;
|
|
47
|
+
case SeverityNumber.ERROR:
|
|
48
|
+
__originalConsole.error(msg);
|
|
49
|
+
break;
|
|
50
|
+
default:
|
|
51
|
+
__originalConsole.log(msg);
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
38
54
|
}
|
|
39
55
|
}
|
|
40
56
|
resultCallback({ code: ExportResultCode.SUCCESS });
|
package/src/otel/otel.ts
CHANGED
|
@@ -102,7 +102,7 @@ export const createAgentuityLoggerProvider = ({
|
|
|
102
102
|
let exporter: OTLPLogExporter | JSONLLogExporter | undefined;
|
|
103
103
|
|
|
104
104
|
if (useConsoleExporters) {
|
|
105
|
-
processor = new SimpleLogRecordProcessor(new ConsoleLogRecordExporter());
|
|
105
|
+
processor = new SimpleLogRecordProcessor(new ConsoleLogRecordExporter(true));
|
|
106
106
|
} else if (jsonlBasePath) {
|
|
107
107
|
exporter = new JSONLLogExporter(jsonlBasePath);
|
|
108
108
|
processor = new BatchLogRecordProcessor(exporter);
|
|
@@ -117,7 +117,7 @@ export const createAgentuityLoggerProvider = ({
|
|
|
117
117
|
exporter = otlpExporter;
|
|
118
118
|
processor = new BatchLogRecordProcessor(otlpExporter);
|
|
119
119
|
} else {
|
|
120
|
-
processor = new SimpleLogRecordProcessor(new ConsoleLogRecordExporter());
|
|
120
|
+
processor = new SimpleLogRecordProcessor(new ConsoleLogRecordExporter(false));
|
|
121
121
|
}
|
|
122
122
|
const provider = new LoggerProvider({
|
|
123
123
|
resource,
|
|
@@ -210,7 +210,10 @@ export function registerOtel(config: OtelConfig): OtelResponse {
|
|
|
210
210
|
const logger = createLogger(!!url, attrs, logLevel);
|
|
211
211
|
|
|
212
212
|
// must do this after we have created the logger
|
|
213
|
-
|
|
213
|
+
// don't patch console if we're using console exporters (to avoid double logging)
|
|
214
|
+
if (!useConsoleExporters) {
|
|
215
|
+
patchConsole(!!url, attrs, logLevel);
|
|
216
|
+
}
|
|
214
217
|
|
|
215
218
|
// Build trace exporter (OTLP or JSONL)
|
|
216
219
|
const traceExporter = jsonlBasePath
|
|
@@ -46,11 +46,10 @@ export class HTTPEvalRunEventProvider implements EvalRunEventProvider {
|
|
|
46
46
|
this.logger.debug('[EVALRUN HTTP] Start event payload: %s', JSON.stringify(payload, null, 2));
|
|
47
47
|
|
|
48
48
|
try {
|
|
49
|
-
const resp = await this.apiClient.
|
|
50
|
-
'POST',
|
|
49
|
+
const resp = await this.apiClient.post(
|
|
51
50
|
endpoint,
|
|
52
|
-
APIResponseSchemaNoData(),
|
|
53
51
|
payload,
|
|
52
|
+
APIResponseSchemaNoData(),
|
|
54
53
|
EvalRunStartEventDelayedSchema
|
|
55
54
|
);
|
|
56
55
|
if (resp.success) {
|
|
@@ -93,11 +92,10 @@ export class HTTPEvalRunEventProvider implements EvalRunEventProvider {
|
|
|
93
92
|
this.logger.debug('[EVALRUN HTTP] Base URL: %s', this.baseUrl);
|
|
94
93
|
|
|
95
94
|
try {
|
|
96
|
-
const resp = await this.apiClient.
|
|
97
|
-
'PUT',
|
|
95
|
+
const resp = await this.apiClient.put(
|
|
98
96
|
endpoint,
|
|
99
|
-
APIResponseSchemaNoData(),
|
|
100
97
|
{ ...event, timestamp: Date.now() },
|
|
98
|
+
APIResponseSchemaNoData(),
|
|
101
99
|
EvalRunCompleteEventDelayedSchema
|
|
102
100
|
);
|
|
103
101
|
if (resp.success) {
|
|
@@ -83,10 +83,16 @@ export class LocalKeyValueStorage implements KeyValueStorage {
|
|
|
83
83
|
buffer = Buffer.from(value);
|
|
84
84
|
} else if (value instanceof ArrayBuffer) {
|
|
85
85
|
buffer = Buffer.from(new Uint8Array(value));
|
|
86
|
-
} else if (
|
|
86
|
+
} else if (
|
|
87
|
+
typeof value === 'number' ||
|
|
88
|
+
typeof value === 'boolean' ||
|
|
89
|
+
typeof value === 'object'
|
|
90
|
+
) {
|
|
91
|
+
// Use JSON for numbers, booleans, and objects to preserve type on round-trip
|
|
87
92
|
buffer = Buffer.from(JSON.stringify(value), 'utf-8');
|
|
88
93
|
contentType = 'application/json';
|
|
89
94
|
} else {
|
|
95
|
+
// Fallback for other types
|
|
90
96
|
buffer = Buffer.from(String(value), 'utf-8');
|
|
91
97
|
}
|
|
92
98
|
|
|
@@ -30,11 +30,10 @@ export class HTTPSessionEventProvider implements SessionEventProvider {
|
|
|
30
30
|
*/
|
|
31
31
|
async start(event: SessionStartEvent): Promise<void> {
|
|
32
32
|
this.logger.debug('Sending session start event: %s', event.id);
|
|
33
|
-
const resp = await this.apiClient.
|
|
34
|
-
'POST',
|
|
33
|
+
const resp = await this.apiClient.post(
|
|
35
34
|
'/session/2025-03-17',
|
|
36
|
-
APIResponseSchemaNoData(),
|
|
37
35
|
{ ...event, timestamp: Date.now() },
|
|
36
|
+
APIResponseSchemaNoData(),
|
|
38
37
|
SessionStartEventDelayedSchema
|
|
39
38
|
);
|
|
40
39
|
if (resp.success) {
|
|
@@ -51,11 +50,10 @@ export class HTTPSessionEventProvider implements SessionEventProvider {
|
|
|
51
50
|
*/
|
|
52
51
|
async complete(event: SessionCompleteEvent): Promise<void> {
|
|
53
52
|
this.logger.debug('Sending session complete event: %s', event.id);
|
|
54
|
-
const resp = await this.apiClient.
|
|
55
|
-
'PUT',
|
|
53
|
+
const resp = await this.apiClient.put(
|
|
56
54
|
'/session/2025-03-17',
|
|
57
|
-
APIResponseSchemaNoData(),
|
|
58
55
|
{ ...event, timestamp: Date.now() },
|
|
56
|
+
APIResponseSchemaNoData(),
|
|
59
57
|
SessionCompleteEventDelayedSchema
|
|
60
58
|
);
|
|
61
59
|
if (resp.success) {
|
package/src/session.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { type Env, fireEvent } from './app';
|
|
|
6
6
|
import type { AppState } from './index';
|
|
7
7
|
import { getServiceUrls } from '@agentuity/server';
|
|
8
8
|
import { WebSocket } from 'ws';
|
|
9
|
+
import { internal } from './logger/internal';
|
|
9
10
|
|
|
10
11
|
export type ThreadEventName = 'destroyed';
|
|
11
12
|
export type SessionEventName = 'completed';
|
|
@@ -418,7 +419,12 @@ async function fireThreadEvent(thread: Thread, eventName: ThreadEventName): Prom
|
|
|
418
419
|
if (!callbacks || callbacks.size === 0) return;
|
|
419
420
|
|
|
420
421
|
for (const callback of callbacks) {
|
|
421
|
-
|
|
422
|
+
try {
|
|
423
|
+
await (callback as any)(eventName, thread);
|
|
424
|
+
} catch (error) {
|
|
425
|
+
// Log but don't re-throw - event listener errors should not crash the server
|
|
426
|
+
internal.error(`Error in thread event listener for '${eventName}':`, error);
|
|
427
|
+
}
|
|
422
428
|
}
|
|
423
429
|
}
|
|
424
430
|
|
|
@@ -431,7 +437,12 @@ async function fireSessionEvent(session: Session, eventName: SessionEventName):
|
|
|
431
437
|
if (!callbacks || callbacks.size === 0) return;
|
|
432
438
|
|
|
433
439
|
for (const callback of callbacks) {
|
|
434
|
-
|
|
440
|
+
try {
|
|
441
|
+
await (callback as any)(eventName, session);
|
|
442
|
+
} catch (error) {
|
|
443
|
+
// Log but don't re-throw - event listener errors should not crash the server
|
|
444
|
+
internal.error(`Error in session event listener for '${eventName}':`, error);
|
|
445
|
+
}
|
|
435
446
|
}
|
|
436
447
|
}
|
|
437
448
|
|
|
@@ -977,8 +988,12 @@ export class DefaultThreadProvider implements ThreadProvider {
|
|
|
977
988
|
if (this.wsClient) {
|
|
978
989
|
try {
|
|
979
990
|
await this.wsClient.delete(thread.id);
|
|
980
|
-
} catch
|
|
981
|
-
|
|
991
|
+
} catch {
|
|
992
|
+
// Thread might not exist in remote storage if it was never persisted
|
|
993
|
+
// This is normal for ephemeral threads, so just log at debug level
|
|
994
|
+
internal.debug(
|
|
995
|
+
`Thread ${thread.id} not found in remote storage (already deleted or never persisted)`
|
|
996
|
+
);
|
|
982
997
|
// Continue with local cleanup even if remote delete fails
|
|
983
998
|
}
|
|
984
999
|
}
|
package/src/workbench.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
1
|
import type { Context, Handler } from 'hono';
|
|
3
|
-
import { s } from '@agentuity/schema';
|
|
4
2
|
import { timingSafeEqual } from 'node:crypto';
|
|
3
|
+
import { toJSONSchema } from '@agentuity/server';
|
|
5
4
|
import { getAgents, createAgentMiddleware } from './agent';
|
|
6
5
|
import { createRouter } from './router';
|
|
7
6
|
import type { WebSocketConnection } from './router';
|
|
@@ -135,25 +134,6 @@ export const createWorkbenchRouter = () => {
|
|
|
135
134
|
return router;
|
|
136
135
|
};
|
|
137
136
|
|
|
138
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
139
|
-
const toJSONSchema = (schema: any) => {
|
|
140
|
-
// Check if it's an Agentuity schema via StandardSchemaV1 vendor
|
|
141
|
-
if (schema?.['~standard']?.vendor === 'agentuity') {
|
|
142
|
-
return s.toJSONSchema(schema);
|
|
143
|
-
}
|
|
144
|
-
// Check if it's a Zod schema
|
|
145
|
-
if (schema?._def?.typeName) {
|
|
146
|
-
try {
|
|
147
|
-
return z.toJSONSchema(schema);
|
|
148
|
-
} catch {
|
|
149
|
-
return {};
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
// TODO: this is going to only work for zod schema for now. need a way to handle others
|
|
153
|
-
// Unknown schema type
|
|
154
|
-
return {};
|
|
155
|
-
};
|
|
156
|
-
|
|
157
137
|
export const createWorkbenchMetadataRoute = (): Handler => {
|
|
158
138
|
const authHeader = process.env.AGENTUITY_WORKBENCH_APIKEY
|
|
159
139
|
? `Bearer ${process.env.AGENTUITY_WORKBENCH_APIKEY}`
|