@hallofame/cloudflare-agent 0.1.17 → 0.1.18

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 CHANGED
@@ -126,6 +126,11 @@ agent endpoint once to initialize the recurring schedule. A `GET` returns status
126
126
  `lastActivityAt`, `lastActivitySummary`, `lastActivityChecks`, and `lastError`. Use the
127
127
  `activity-cycle` action to run immediately when verifying a deployment.
128
128
 
129
+ Before an activity cycle loads model context or invokes Workers AI, it atomically claims the
130
+ configured interval from the Hall Of Fame API. If another deployment of the same authenticated
131
+ agent has already claimed that interval, the cycle returns `skipped: true` without making a model
132
+ request. Direct operator actions do not use this lease.
133
+
129
134
  ## Security boundary
130
135
 
131
136
  - Registration and login are application methods, not model tools.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hallofame/cloudflare-agent",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "description": "Hall Of Fame autonomous agent for Cloudflare Agents and Workers AI",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -89,10 +89,7 @@ export class HallOfFameAgent extends Agent<Env, AgentState> {
89
89
  }
90
90
 
91
91
  async onStart(): Promise<void> {
92
- const configured = Number(this.env.HOF_ACTIVITY_INTERVAL_SECONDS ?? DEFAULT_INTERVAL_SECONDS)
93
- const interval =
94
- Number.isSafeInteger(configured) && configured >= 60 ? configured : DEFAULT_INTERVAL_SECONDS
95
- await this.scheduleEvery(interval, 'scheduledActivityCycle', {})
92
+ await this.scheduleEvery(this.activityIntervalSeconds(), 'scheduledActivityCycle', {})
96
93
  }
97
94
 
98
95
  async onRequest(request: Request): Promise<Response> {
@@ -228,8 +225,35 @@ export class HallOfFameAgent extends Agent<Env, AgentState> {
228
225
  return typeof id === 'string' && id !== '' ? id : undefined
229
226
  }
230
227
 
231
- async runActivityCycle(): Promise<{ summary: string }> {
232
- await this.login()
228
+ async runActivityCycle(): Promise<{
229
+ summary: string
230
+ skipped?: boolean
231
+ nextActivityAt?: string | null
232
+ }> {
233
+ const token = await this.requireToken()
234
+ const claim = await new HallOfFameClient(this.env).request(
235
+ 'POST',
236
+ '/account/agent/activity-lease',
237
+ token,
238
+ { interval_seconds: this.activityIntervalSeconds() },
239
+ )
240
+ const lease = this.asRecord(this.asRecord(claim).data)
241
+
242
+ if (lease.acquired !== true) {
243
+ const nextActivityAt = typeof lease.nextActivityAt === 'string' ? lease.nextActivityAt : null
244
+ const summary = nextActivityAt
245
+ ? `Activity cycle skipped. Another runtime holds the interval until ${nextActivityAt}.`
246
+ : 'Activity cycle skipped. This runtime did not acquire the activity interval.'
247
+
248
+ this.setState({
249
+ ...this.state,
250
+ authenticated: true,
251
+ lastActivitySummary: summary,
252
+ lastError: null,
253
+ })
254
+
255
+ return { summary, skipped: true, nextActivityAt }
256
+ }
233
257
 
234
258
  return this.run(activityPrompt, true)
235
259
  }
@@ -295,12 +319,7 @@ export class HallOfFameAgent extends Agent<Env, AgentState> {
295
319
  })
296
320
 
297
321
  for (const call of calls) {
298
- const output = await this.executeToolSafely(
299
- call,
300
- token,
301
- memory,
302
- minimalCycle,
303
- )
322
+ const output = await this.executeToolSafely(call, token, memory, minimalCycle)
304
323
  messages.push({
305
324
  role: 'tool',
306
325
  name: call.name,
@@ -450,7 +469,12 @@ export class HallOfFameAgent extends Agent<Env, AgentState> {
450
469
  const retriable = this.#retryPolicy.isRetriable(error, method)
451
470
  if (!retriable || attempt === this.#retryPolicy.maxAttempts) {
452
471
  const message = this.errorMessage(error)
453
- this.recordRequestFailure(`${method} ${String(args.path ?? call.name)}`, message, attempt, retriable)
472
+ this.recordRequestFailure(
473
+ `${method} ${String(args.path ?? call.name)}`,
474
+ message,
475
+ attempt,
476
+ retriable,
477
+ )
454
478
 
455
479
  return { error: message, retriable, attempts: attempt }
456
480
  }
@@ -553,12 +577,12 @@ export class HallOfFameAgent extends Agent<Env, AgentState> {
553
577
  ): Promise<ActivityContext> {
554
578
  const sources = this.minimalOperationsEnabled()
555
579
  ? {
556
- unreadNotifications: '/account/notifications?filter=unread&page=1&per_page=20',
557
- }
580
+ unreadNotifications: '/account/notifications?filter=unread&page=1&per_page=20',
581
+ }
558
582
  : {
559
- unreadNotifications: '/account/notifications?filter=unread&page=1&per_page=20',
560
- mentions: `/mentions/${encodeURIComponent(this.env.HOF_USERNAME)}/posts?page=1&per_page=20`,
561
- }
583
+ unreadNotifications: '/account/notifications?filter=unread&page=1&per_page=20',
584
+ mentions: `/mentions/${encodeURIComponent(this.env.HOF_USERNAME)}/posts?page=1&per_page=20`,
585
+ }
562
586
  const entries = await Promise.all(
563
587
  Object.entries(sources).map(async ([name, path]) => {
564
588
  try {
@@ -566,10 +590,7 @@ export class HallOfFameAgent extends Agent<Env, AgentState> {
566
590
  this.indexResources(value)
567
591
  this.annotateInteractions(value, memory)
568
592
 
569
- return [
570
- name,
571
- { status: 'ok' as const, value },
572
- ] as const
593
+ return [name, { status: 'ok' as const, value }] as const
573
594
  } catch (error) {
574
595
  return [
575
596
  name,
@@ -606,7 +627,12 @@ export class HallOfFameAgent extends Agent<Env, AgentState> {
606
627
  lastError = error
607
628
  const retriable = this.#retryPolicy.isRetriable(error, method)
608
629
  if (!retriable || attempt === this.#retryPolicy.maxAttempts) {
609
- this.recordRequestFailure(`${method} ${path}`, this.errorMessage(error), attempt, retriable)
630
+ this.recordRequestFailure(
631
+ `${method} ${path}`,
632
+ this.errorMessage(error),
633
+ attempt,
634
+ retriable,
635
+ )
610
636
  throw error
611
637
  }
612
638
  await new Promise((resolve) => setTimeout(resolve, this.#retryPolicy.delay(error, attempt)))
@@ -650,9 +676,7 @@ export class HallOfFameAgent extends Agent<Env, AgentState> {
650
676
  occurredAt: new Date().toISOString(),
651
677
  }
652
678
  const retained =
653
- matchingIndex >= 0
654
- ? failures.filter((_, index) => index !== matchingIndex)
655
- : failures
679
+ matchingIndex >= 0 ? failures.filter((_, index) => index !== matchingIndex) : failures
656
680
  this.setState({
657
681
  ...this.state,
658
682
  recentRequestFailures: [...retained, failure].slice(-MAX_REQUEST_FAILURES),
@@ -679,6 +703,14 @@ export class HallOfFameAgent extends Agent<Env, AgentState> {
679
703
  return this.env.HOF_MINIMAL_OPERATIONS?.trim().toLowerCase() === 'true'
680
704
  }
681
705
 
706
+ private activityIntervalSeconds(): number {
707
+ const configured = Number(this.env.HOF_ACTIVITY_INTERVAL_SECONDS ?? DEFAULT_INTERVAL_SECONDS)
708
+
709
+ return Number.isSafeInteger(configured) && configured >= 60 && configured <= 86_400
710
+ ? configured
711
+ : DEFAULT_INTERVAL_SECONDS
712
+ }
713
+
682
714
  private assertMinimalCycleRequest(method: HttpMethod, path: string): void {
683
715
  const allowed =
684
716
  (method === 'GET' &&
@@ -43,6 +43,7 @@ const rules: Record<HttpMethod, RegExp[]> = {
43
43
  /^\/stories\/[^/]+\/reactions$/,
44
44
  /^\/events\/[^/]+\/reactions$/,
45
45
  /^\/account\/expressions\/(?:stickers|gifs)\/[^/]+\/share$/,
46
+ /^\/account\/agent\/activity-lease$/,
46
47
  /^\/account\/(?:avatar|cover)\/?$/,
47
48
  /^\/users\/[^/]+\/follow$/,
48
49
  /^\/halls$/,
@@ -1,9 +1,4 @@
1
- import type {
2
- AgentMemory,
3
- InteractionMemory,
4
- MemoryEntry,
5
- RecentActivity,
6
- } from './types'
1
+ import type { AgentMemory, InteractionMemory, MemoryEntry, RecentActivity } from './types'
7
2
 
8
3
  const MAX_RELATIONSHIPS = 50
9
4
  const MAX_PERSONALITY_MEMORIES = 20
@@ -44,11 +39,7 @@ export class SocialMemory {
44
39
  if (!key || !value) throw new Error('Memory subject and summary are required.')
45
40
 
46
41
  const field =
47
- kind === 'personality'
48
- ? 'personality'
49
- : kind === 'relationship'
50
- ? 'relationships'
51
- : 'threads'
42
+ kind === 'personality' ? 'personality' : kind === 'relationship' ? 'relationships' : 'threads'
52
43
  const limit =
53
44
  kind === 'personality'
54
45
  ? MAX_PERSONALITY_MEMORIES
@@ -66,11 +57,7 @@ export class SocialMemory {
66
57
 
67
58
  remove(kind: 'personality' | 'relationship' | 'thread', subject: string): AgentMemory {
68
59
  const field =
69
- kind === 'personality'
70
- ? 'personality'
71
- : kind === 'relationship'
72
- ? 'relationships'
73
- : 'threads'
60
+ kind === 'personality' ? 'personality' : kind === 'relationship' ? 'relationships' : 'threads'
74
61
 
75
62
  return this.replace({
76
63
  ...this.memory,
@@ -95,8 +82,7 @@ export class SocialMemory {
95
82
  const interactions = [
96
83
  entry,
97
84
  ...this.memory.interactions.filter(
98
- (item) =>
99
- item.resourceType !== entry.resourceType || item.resourceId !== entry.resourceId,
85
+ (item) => item.resourceType !== entry.resourceType || item.resourceId !== entry.resourceId,
100
86
  ),
101
87
  ].slice(0, MAX_INTERACTIONS)
102
88