@animalabs/agent-framework 0.6.3 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/src/agent.d.ts +3 -1
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +27 -2
- package/dist/src/agent.js.map +1 -1
- package/dist/src/framework.d.ts +46 -1
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +280 -146
- package/dist/src/framework.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/modules/discord/index.d.ts +1 -0
- package/dist/src/modules/discord/index.d.ts.map +1 -1
- package/dist/src/modules/discord/index.js +4 -1
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/discord/types.d.ts +2 -0
- package/dist/src/modules/discord/types.d.ts.map +1 -1
- package/dist/src/modules/health/index.d.ts +2 -0
- package/dist/src/modules/health/index.d.ts.map +1 -1
- package/dist/src/modules/health/index.js +15 -4
- package/dist/src/modules/health/index.js.map +1 -1
- package/dist/src/timezone.d.ts +14 -0
- package/dist/src/timezone.d.ts.map +1 -0
- package/dist/src/timezone.js +74 -0
- package/dist/src/timezone.js.map +1 -0
- package/dist/src/types/agent.d.ts +5 -2
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +15 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/test/activation-request-sanitize.test.d.ts +2 -0
- package/dist/test/activation-request-sanitize.test.d.ts.map +1 -0
- package/dist/test/activation-request-sanitize.test.js +89 -0
- package/dist/test/activation-request-sanitize.test.js.map +1 -0
- package/dist/test/agent-cache-ttl.test.js +18 -2
- package/dist/test/agent-cache-ttl.test.js.map +1 -1
- package/dist/test/ephemeral-settle.test.d.ts +2 -0
- package/dist/test/ephemeral-settle.test.d.ts.map +1 -0
- package/dist/test/ephemeral-settle.test.js +351 -0
- package/dist/test/ephemeral-settle.test.js.map +1 -0
- package/dist/test/health-snapshot.test.d.ts +2 -0
- package/dist/test/health-snapshot.test.d.ts.map +1 -0
- package/dist/test/health-snapshot.test.js +74 -0
- package/dist/test/health-snapshot.test.js.map +1 -0
- package/dist/test/maintenance-timer.test.js +49 -0
- package/dist/test/maintenance-timer.test.js.map +1 -1
- package/dist/test/overbudget-drain-breaker.test.d.ts +2 -0
- package/dist/test/overbudget-drain-breaker.test.d.ts.map +1 -0
- package/dist/test/overbudget-drain-breaker.test.js +157 -0
- package/dist/test/overbudget-drain-breaker.test.js.map +1 -0
- package/dist/test/timezone.test.d.ts +2 -0
- package/dist/test/timezone.test.d.ts.map +1 -0
- package/dist/test/timezone.test.js +18 -0
- package/dist/test/timezone.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/agent.ts +30 -3
- package/src/framework.ts +313 -146
- package/src/index.ts +1 -0
- package/src/modules/discord/index.ts +4 -1
- package/src/modules/discord/types.ts +3 -0
- package/src/modules/health/index.ts +20 -4
- package/src/timezone.ts +90 -0
- package/src/types/agent.ts +6 -2
- package/src/types/framework.ts +17 -0
package/src/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export { Agent } from './agent.js';
|
|
|
7
7
|
export type { StartStreamResult } from './agent.js';
|
|
8
8
|
export { ProcessQueueImpl } from './queue.js';
|
|
9
9
|
export { ModuleRegistry } from './module-registry.js';
|
|
10
|
+
export { formatZonedDateTime, formatZonedTime, isValidTimeZone, resolveTimeZone } from './timezone.js';
|
|
10
11
|
|
|
11
12
|
// Built-in modules
|
|
12
13
|
export * from './modules/index.js';
|
|
@@ -41,6 +41,7 @@ import type {
|
|
|
41
41
|
ListChannelsInput,
|
|
42
42
|
FetchHistoryInput,
|
|
43
43
|
} from './types.js';
|
|
44
|
+
import { formatZonedDateTime, resolveTimeZone } from '../../timezone.js';
|
|
44
45
|
|
|
45
46
|
// Re-export event types for consumers who want to handle them
|
|
46
47
|
export type { DiscordMessageEvent, DiscordEditEvent, DiscordDeleteEvent } from './types.js';
|
|
@@ -99,6 +100,7 @@ export class DiscordModule implements Module {
|
|
|
99
100
|
readonly name = 'discord';
|
|
100
101
|
|
|
101
102
|
private config: DiscordModuleConfig;
|
|
103
|
+
private readonly timeZone: string;
|
|
102
104
|
private ctx: ModuleContext | null = null;
|
|
103
105
|
private client: DiscordClientInterface;
|
|
104
106
|
private state: DiscordModuleState = {
|
|
@@ -126,6 +128,7 @@ export class DiscordModule implements Module {
|
|
|
126
128
|
constructor(client: DiscordClientInterface, config: DiscordModuleConfig) {
|
|
127
129
|
this.client = client;
|
|
128
130
|
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
131
|
+
this.timeZone = resolveTimeZone(config.timeZone);
|
|
129
132
|
}
|
|
130
133
|
|
|
131
134
|
async start(ctx: ModuleContext): Promise<void> {
|
|
@@ -1190,7 +1193,7 @@ export class DiscordModule implements Module {
|
|
|
1190
1193
|
authorId: m.authorId,
|
|
1191
1194
|
isBot: m.isBot,
|
|
1192
1195
|
content: m.content,
|
|
1193
|
-
timestamp: m.timestamp.
|
|
1196
|
+
timestamp: formatZonedDateTime(m.timestamp, this.timeZone),
|
|
1194
1197
|
replyTo: m.replyTo,
|
|
1195
1198
|
})),
|
|
1196
1199
|
},
|
|
@@ -9,6 +9,9 @@ import type { ContentBlock } from '@animalabs/membrane';
|
|
|
9
9
|
// ============================================================================
|
|
10
10
|
|
|
11
11
|
export interface DiscordModuleConfig {
|
|
12
|
+
/** IANA zone for timestamps returned to the agent by fetch_history. */
|
|
13
|
+
timeZone?: string;
|
|
14
|
+
|
|
12
15
|
/** Discord bot token */
|
|
13
16
|
token: string;
|
|
14
17
|
|
|
@@ -24,8 +24,12 @@ import type { Module, ModuleContext } from '../../types/module.js';
|
|
|
24
24
|
import type { ToolDefinition, ToolCall, ToolResult, ProcessEvent } from '../../types/events.js';
|
|
25
25
|
import type { EventResponse, ProcessState } from '../../types/module.js';
|
|
26
26
|
import type { AgentFramework } from '../../framework.js';
|
|
27
|
+
import { formatZonedDateTime, resolveTimeZone } from '../../timezone.js';
|
|
27
28
|
|
|
28
29
|
export interface HealthModuleConfig {
|
|
30
|
+
/** IANA zone for timestamps returned by the snapshot tool. */
|
|
31
|
+
timeZone?: string;
|
|
32
|
+
|
|
29
33
|
/**
|
|
30
34
|
* Default number of recent inferences to summarize when the tool is
|
|
31
35
|
* called without an explicit limit. Default: 20.
|
|
@@ -70,6 +74,7 @@ export class HealthModule implements Module {
|
|
|
70
74
|
|
|
71
75
|
constructor(config: HealthModuleConfig = {}) {
|
|
72
76
|
this.config = {
|
|
77
|
+
timeZone: resolveTimeZone(config.timeZone),
|
|
73
78
|
defaultLookback: config.defaultLookback ?? 20,
|
|
74
79
|
subagentStateId: config.subagentStateId ?? 'modules/subagent/state',
|
|
75
80
|
};
|
|
@@ -162,10 +167,20 @@ export class HealthModule implements Module {
|
|
|
162
167
|
const recent = this.framework.queryInferenceLogs({ limit: lookback });
|
|
163
168
|
const recentEntries = recent.entries ?? [];
|
|
164
169
|
|
|
170
|
+
// Presentation-boundary guard: queryInferenceLogs casts raw persisted
|
|
171
|
+
// JSON without validation, so a legacy/corrupt record can carry a missing
|
|
172
|
+
// or non-numeric timestamp. formatZonedDateTime throws on invalid dates
|
|
173
|
+
// (correctly — misuse should be loud), but one bad record must not sink
|
|
174
|
+
// the whole snapshot: degrade to the raw value (or null) instead.
|
|
175
|
+
const safeZoned = (ts: unknown): unknown =>
|
|
176
|
+
typeof ts === 'number' && Number.isFinite(ts)
|
|
177
|
+
? formatZonedDateTime(ts, this.config.timeZone)
|
|
178
|
+
: ts ?? null;
|
|
179
|
+
|
|
165
180
|
const projectSummary = (e: typeof recentEntries[number]) => {
|
|
166
181
|
const s = e.summary ?? e.entry;
|
|
167
182
|
return {
|
|
168
|
-
timestamp: s.timestamp,
|
|
183
|
+
timestamp: safeZoned(s.timestamp),
|
|
169
184
|
agentName: s.agentName,
|
|
170
185
|
success: s.success,
|
|
171
186
|
error: s.error,
|
|
@@ -224,8 +239,8 @@ export class HealthModule implements Module {
|
|
|
224
239
|
name: sa.name,
|
|
225
240
|
type: sa.type,
|
|
226
241
|
status: sa.status,
|
|
227
|
-
startedAt: startedAt > 0 ?
|
|
228
|
-
lastActivityAt: lastActivityAt > 0 ?
|
|
242
|
+
startedAt: startedAt > 0 ? formatZonedDateTime(startedAt, this.config.timeZone) : null,
|
|
243
|
+
lastActivityAt: lastActivityAt > 0 ? formatZonedDateTime(lastActivityAt, this.config.timeZone) : null,
|
|
229
244
|
runtimeSeconds: startedAt > 0 ? Math.floor((now - startedAt) / 1000) : null,
|
|
230
245
|
silentSeconds: lastActivityAt > 0 ? Math.floor((now - lastActivityAt) / 1000) : null,
|
|
231
246
|
toolCallsCount: sa.toolCallsCount,
|
|
@@ -244,7 +259,8 @@ export class HealthModule implements Module {
|
|
|
244
259
|
const modules = this.ctx ? this.listModulesViaContext() : [];
|
|
245
260
|
|
|
246
261
|
return {
|
|
247
|
-
timestamp:
|
|
262
|
+
timestamp: formatZonedDateTime(Date.now(), this.config.timeZone),
|
|
263
|
+
timeZone: this.config.timeZone,
|
|
248
264
|
window: {
|
|
249
265
|
lookback,
|
|
250
266
|
inferencesInWindow: recentEntries.length,
|
package/src/timezone.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-visible wall-clock formatting.
|
|
3
|
+
*
|
|
4
|
+
* Persistence and protocol timestamps should remain epoch milliseconds or UTC
|
|
5
|
+
* ISO strings. These helpers are only for presentation boundaries where a
|
|
6
|
+
* timestamp is rendered into an agent's context or a tool result.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export function isValidTimeZone(timeZone: string): boolean {
|
|
10
|
+
try {
|
|
11
|
+
new Intl.DateTimeFormat('en-US', { timeZone }).format(new Date(0));
|
|
12
|
+
return true;
|
|
13
|
+
} catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function resolveTimeZone(configured?: string): string {
|
|
19
|
+
const candidate = configured?.trim()
|
|
20
|
+
|| process.env.AGENT_TIMEZONE?.trim()
|
|
21
|
+
|| Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
22
|
+
|| 'UTC';
|
|
23
|
+
if (!isValidTimeZone(candidate)) {
|
|
24
|
+
throw new Error(`Invalid IANA time zone: ${JSON.stringify(candidate)}`);
|
|
25
|
+
}
|
|
26
|
+
return candidate;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type ZonedParts = {
|
|
30
|
+
year: string;
|
|
31
|
+
month: string;
|
|
32
|
+
day: string;
|
|
33
|
+
hour: string;
|
|
34
|
+
minute: string;
|
|
35
|
+
second: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function zonedParts(date: Date, timeZone: string): ZonedParts {
|
|
39
|
+
const parts = new Intl.DateTimeFormat('en-CA', {
|
|
40
|
+
timeZone,
|
|
41
|
+
year: 'numeric',
|
|
42
|
+
month: '2-digit',
|
|
43
|
+
day: '2-digit',
|
|
44
|
+
hour: '2-digit',
|
|
45
|
+
minute: '2-digit',
|
|
46
|
+
second: '2-digit',
|
|
47
|
+
hourCycle: 'h23',
|
|
48
|
+
}).formatToParts(date);
|
|
49
|
+
const get = (type: Intl.DateTimeFormatPartTypes): string =>
|
|
50
|
+
parts.find((part) => part.type === type)?.value ?? '';
|
|
51
|
+
return {
|
|
52
|
+
year: get('year'),
|
|
53
|
+
month: get('month'),
|
|
54
|
+
day: get('day'),
|
|
55
|
+
hour: get('hour'),
|
|
56
|
+
minute: get('minute'),
|
|
57
|
+
second: get('second'),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** RFC3339-style local time with an explicit numeric offset and IANA zone. */
|
|
62
|
+
export function formatZonedDateTime(value: Date | number | string, timeZone: string): string {
|
|
63
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
64
|
+
if (Number.isNaN(date.getTime())) throw new Error(`Invalid date: ${String(value)}`);
|
|
65
|
+
const zone = resolveTimeZone(timeZone);
|
|
66
|
+
const p = zonedParts(date, zone);
|
|
67
|
+
|
|
68
|
+
// Reinterpret the displayed wall-clock components as UTC to recover the
|
|
69
|
+
// offset that applied at this exact instant (including DST transitions).
|
|
70
|
+
const displayedAsUtc = Date.UTC(
|
|
71
|
+
Number(p.year), Number(p.month) - 1, Number(p.day),
|
|
72
|
+
Number(p.hour), Number(p.minute), Number(p.second),
|
|
73
|
+
);
|
|
74
|
+
const instantAtWholeSecond = Math.floor(date.getTime() / 1000) * 1000;
|
|
75
|
+
const offsetMinutes = Math.round((displayedAsUtc - instantAtWholeSecond) / 60_000);
|
|
76
|
+
const sign = offsetMinutes < 0 ? '-' : '+';
|
|
77
|
+
const absoluteOffset = Math.abs(offsetMinutes);
|
|
78
|
+
const offset = `${sign}${String(Math.floor(absoluteOffset / 60)).padStart(2, '0')}:${String(absoluteOffset % 60).padStart(2, '0')}`;
|
|
79
|
+
const millis = String(date.getUTCMilliseconds()).padStart(3, '0');
|
|
80
|
+
|
|
81
|
+
return `${p.year}-${p.month}-${p.day}T${p.hour}:${p.minute}:${p.second}.${millis}${offset} [${zone}]`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Compact wall-clock time for provenance headers. */
|
|
85
|
+
export function formatZonedTime(value: Date | number | string, timeZone: string): string {
|
|
86
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
87
|
+
if (Number.isNaN(date.getTime())) throw new Error(`Invalid date: ${String(value)}`);
|
|
88
|
+
const p = zonedParts(date, resolveTimeZone(timeZone));
|
|
89
|
+
return `${p.hour}:${p.minute}`;
|
|
90
|
+
}
|
package/src/types/agent.ts
CHANGED
|
@@ -48,7 +48,7 @@ export interface AgentConfig {
|
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* Prompt-cache TTL forwarded to the provider (Anthropic `cache_control.ttl`).
|
|
51
|
-
* '5m'
|
|
51
|
+
* '5m' or '1h'. Defaults to '1h'.
|
|
52
52
|
*
|
|
53
53
|
* Why you'd set '1h': for persistent agents whose conversational cadence is
|
|
54
54
|
* slower than 5 minutes, the default TTL expires between turns and the full
|
|
@@ -57,10 +57,14 @@ export interface AgentConfig {
|
|
|
57
57
|
* chatty-but-not-rapid agent pays the write premium over and over. With '1h'
|
|
58
58
|
* the write happens once per idle-hour and subsequent turns hit cache reads;
|
|
59
59
|
* in practice cache writes can dominate spend for slow-cadence agents.
|
|
60
|
-
*
|
|
60
|
+
* Set '5m' explicitly for high-frequency loops with sub-5-minute cadence,
|
|
61
61
|
* where the cheaper write premium wins.
|
|
62
62
|
*/
|
|
63
63
|
cacheTtl?: '5m' | '1h';
|
|
64
|
+
|
|
65
|
+
/** Provider-specific request parameters (for example Responses reasoning
|
|
66
|
+
* and server-side compaction settings). */
|
|
67
|
+
providerParams?: Record<string, unknown>;
|
|
64
68
|
/**
|
|
65
69
|
* Extended thinking config. When `enabled: true`, the agent runs with
|
|
66
70
|
* Anthropic's native extended thinking; responses include `thinking` blocks
|
package/src/types/framework.ts
CHANGED
|
@@ -32,6 +32,13 @@ export interface ProcessLoggingConfig {
|
|
|
32
32
|
* Configuration for the agent framework.
|
|
33
33
|
*/
|
|
34
34
|
export interface FrameworkConfig {
|
|
35
|
+
/**
|
|
36
|
+
* IANA zone used only when rendering wall-clock times for the agent.
|
|
37
|
+
* Stored/protocol timestamps remain epoch/UTC. Defaults to AGENT_TIMEZONE,
|
|
38
|
+
* then the process zone.
|
|
39
|
+
*/
|
|
40
|
+
timeZone?: string;
|
|
41
|
+
|
|
35
42
|
/** Path to Chronicle store */
|
|
36
43
|
storePath?: string;
|
|
37
44
|
|
|
@@ -174,6 +181,16 @@ export type ErrorAction =
|
|
|
174
181
|
| { retry: true; delayMs: number }
|
|
175
182
|
| { retry: false; emit?: ProcessEvent };
|
|
176
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Terminal result for a framework-driven ephemeral agent run.
|
|
186
|
+
*/
|
|
187
|
+
export interface AgentSettleResult {
|
|
188
|
+
stopReason: 'completed' | 'turn_ended' | 'exhausted';
|
|
189
|
+
speech: string;
|
|
190
|
+
toolCallsCount: number;
|
|
191
|
+
error?: string;
|
|
192
|
+
}
|
|
193
|
+
|
|
177
194
|
/**
|
|
178
195
|
* Framework state exposed to policies.
|
|
179
196
|
*/
|