ljsysfurry commited on
Commit
aef5124
·
verified ·
1 Parent(s): 16a8ad8

sync cleaned docs: dsh-plugin/aura-scheduler/src/index.ts

Browse files
dsh-plugin/aura-scheduler/src/index.ts ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * dsh-aura-scheduler — proactive scheduling for DeepSeek Harness.
3
+ *
4
+ * Official dsh-schedule is model-driven: the model calls schedule_create to
5
+ * create reminders. Aura is the OPPOSITE: the system decides when the agent
6
+ * should proactively reach out, based on:
7
+ * - adaptive heartbeat (idle longer → more eager)
8
+ * - value network V = α·urgency + β·relevance − δ·interruption
9
+ * - quiet hours (no proactive messages at night)
10
+ * - anti-harassment cooldown
11
+ *
12
+ * This is the "AI knows when to speak" layer the official harness lacks.
13
+ *
14
+ * @module @agentframe/dsh-aura-scheduler
15
+ */
16
+
17
+ import { Context } from '@deepseek-ai/cordis'
18
+ import z from '@deepseek-ai/schemastery'
19
+
20
+ export interface AuraSchedulerConfig {
21
+ /** Minimum interval between proactive messages (seconds). */
22
+ minInterval: number
23
+ /** Maximum interval (seconds). */
24
+ maxInterval: number
25
+ /** Quiet hours: [startHour, endHour] in 24h, e.g. [23, 8]. */
26
+ quietHours: [number, number]
27
+ /** Alpha (urgency weight), beta (relevance), delta (interruption penalty). */
28
+ alpha: number
29
+ beta: number
30
+ relevance: number
31
+ delta: number
32
+ /** Cooldown after last proactive message (seconds). */
33
+ cooldown: number
34
+ /** Callback invoked when the agent should proactively speak. */
35
+ onProactive: string
36
+ /** Auto-start the heartbeat. */
37
+ auto: boolean
38
+ }
39
+
40
+ const DEFAULT_CONFIG: AuraSchedulerConfig = {
41
+ minInterval: 1800, // 30 min
42
+ maxInterval: 7200, // 2 hours
43
+ quietHours: [23, 8],
44
+ alpha: 0.4,
45
+ beta: 0.4,
46
+ relevance: 0.5,
47
+ delta: 0.2,
48
+ cooldown: 600,
49
+ onProactive: '',
50
+ auto: true,
51
+ }
52
+
53
+ /**
54
+ * AuraSchedulerService — exposes ctx.aura (schedule/tick/status) and runs
55
+ * the proactive heartbeat.
56
+ */
57
+ export class AuraSchedulerService {
58
+ static inject: string[] = []
59
+
60
+ static Config: z<AuraSchedulerConfig> = z.object({
61
+ minInterval: z.number().default(1800),
62
+ maxInterval: z.number().default(7200),
63
+ quietHours: z.tuple([z.number(), z.number()]).default([23, 8] as [number, number]),
64
+ alpha: z.number().default(0.4),
65
+ beta: z.number().default(0.4),
66
+ relevance: z.number().default(0.5),
67
+ delta: z.number().default(0.2),
68
+ cooldown: z.number().default(600),
69
+ onProactive: z.string().default(''),
70
+ auto: z.boolean().default(true),
71
+ })
72
+
73
+ readonly config: AuraSchedulerConfig
74
+ private timer: ReturnType<typeof setInterval> | null = null
75
+ private lastProactiveAt = 0
76
+ private heartbeatCount = 0
77
+ private proactiveCount = 0
78
+ private readonly ctx: Context
79
+
80
+ constructor(ctx: Context, config: Partial<AuraSchedulerConfig> = {}) {
81
+ this.ctx = ctx
82
+ this.config = { ...DEFAULT_CONFIG, ...config }
83
+ ;(ctx as any).provide?.('aura')
84
+ ctx.set('aura', this)
85
+ if (this.config.auto) this.start()
86
+ }
87
+
88
+ // ===== Public API (ctx.aura) =====
89
+
90
+ start(): void {
91
+ if (this.timer) return
92
+ this._tick() // immediate first check
93
+ this.timer = setInterval(() => this._tick(), 60_000)
94
+ this.ctx.logger.info(
95
+ `[aura] started: ${this.config.minInterval / 60}-${this.config.maxInterval / 60}min heartbeat, quiet ${this.config.quietHours[0]}:00-${this.config.quietHours[1]}:00`,
96
+ )
97
+ }
98
+
99
+ stop(): void {
100
+ if (this.timer) {
101
+ clearInterval(this.timer)
102
+ this.timer = null
103
+ }
104
+ }
105
+
106
+ status(): Record<string, unknown> {
107
+ return {
108
+ running: this.timer !== null,
109
+ heartbeatCount: this.heartbeatCount,
110
+ proactiveCount: this.proactiveCount,
111
+ lastProactiveAt: this.lastProactiveAt,
112
+ config: this.config,
113
+ }
114
+ }
115
+
116
+ /** Force a proactive tick (useful for tests / manual trigger). */
117
+ async tickNow(): Promise<boolean> {
118
+ return this._maybeProactive(true)
119
+ }
120
+
121
+ // ===== Internals =====
122
+
123
+ private _tick(): void {
124
+ this.heartbeatCount++
125
+ void this._maybeProactive(false)
126
+ }
127
+
128
+ /** Aura value network + heartbeat policy. */
129
+ private _shouldAct(force: boolean): boolean {
130
+ const now = Date.now()
131
+
132
+ // Quiet hours (force bypasses).
133
+ if (!force && this._inQuietHours()) {
134
+ this.ctx.logger.info('[aura] quiet hours, skip')
135
+ return false
136
+ }
137
+
138
+ // Cooldown (force bypasses).
139
+ if (!force && now - this.lastProactiveAt < this.config.cooldown * 1000) {
140
+ return false
141
+ }
142
+
143
+ // Adaptive heartbeat: idle longer → higher eagerness.
144
+ const idleRatio = Math.min((now - this.lastProactiveAt) / 1000 / this.config.maxInterval, 1)
145
+ const eagerness = this.config.alpha * idleRatio + this.config.beta * this.config.relevance
146
+ const interruption = this.config.delta * this._interruptionCost()
147
+
148
+ // Value network: V = α·urgency + β·relevance − δ·interruption
149
+ const value = eagerness - interruption
150
+ this.ctx.logger.debug(`[aura] value=${value.toFixed(3)} (eager=${eagerness.toFixed(3)} intr=${interruption.toFixed(3)})`)
151
+
152
+ if (force) return value > -0.1
153
+ // Act when value crosses the bar OR heartbeat exceeded max interval.
154
+ const overdue = now - this.lastProactiveAt > this.config.maxInterval * 1000
155
+ return value > 0.2 || overdue
156
+ }
157
+
158
+ private async _maybeProactive(force: boolean): Promise<boolean> {
159
+ if (!this._shouldAct(force)) return false
160
+ this.lastProactiveAt = Date.now()
161
+ this.proactiveCount++
162
+
163
+ // Deliver via configured callback (e.g. "notify" event or a webhook URL).
164
+ const cb = this.config.onProactive
165
+ if (cb) {
166
+ try {
167
+ if (cb.startsWith('http')) {
168
+ await fetch(cb, { method: 'POST', body: JSON.stringify({ event: 'proactive', at: new Date().toISOString() }) })
169
+ } else {
170
+ this.ctx.emit(cb as any, { at: new Date().toISOString() })
171
+ }
172
+ this.ctx.logger.info(`[aura] proactive #${this.proactiveCount} delivered via ${cb}`)
173
+ } catch (e) {
174
+ this.ctx.logger.warn('[aura] proactive delivery failed:', e)
175
+ }
176
+ } else {
177
+ this.ctx.logger.info(`[aura] proactive #${this.proactiveCount} (no onProactive configured)`)
178
+ }
179
+ return true
180
+ }
181
+
182
+ private _inQuietHours(): boolean {
183
+ const h = new Date().getHours()
184
+ const [start, end] = this.config.quietHours
185
+ if (start < end) return h >= start && h < end
186
+ return h >= start || h < end
187
+ }
188
+
189
+ private _interruptionCost(): number {
190
+ // Placeholder: could read session activity / user presence.
191
+ return 0.3
192
+ }
193
+ }
194
+
195
+ export default AuraSchedulerService