@tloncorp/openclaw 0.2.0 → 0.4.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/README.md +232 -23
- package/dist/index.js +204 -69
- package/dist/index.js.map +1 -1
- package/dist/setup-api.js +3 -0
- package/dist/setup-api.js.map +1 -0
- package/dist/setup-entry.js +4 -0
- package/dist/setup-entry.js.map +1 -0
- package/dist/src/actions.js +30 -8
- package/dist/src/actions.js.map +1 -1
- package/dist/src/channel.js +155 -420
- package/dist/src/channel.js.map +1 -1
- package/dist/src/channel.runtime.js +142 -0
- package/dist/src/channel.runtime.js.map +1 -0
- package/dist/src/config-schema.js +37 -1
- package/dist/src/config-schema.js.map +1 -1
- package/dist/src/effective-owner.js +22 -0
- package/dist/src/effective-owner.js.map +1 -0
- package/dist/src/gateway-status.js +72 -0
- package/dist/src/gateway-status.js.map +1 -0
- package/dist/src/heartbeat-telemetry.js +155 -0
- package/dist/src/heartbeat-telemetry.js.map +1 -0
- package/dist/src/monitor/approval.js.map +1 -1
- package/dist/src/monitor/computing-presence.js +221 -0
- package/dist/src/monitor/computing-presence.js.map +1 -0
- package/dist/src/monitor/discovery.js +2 -2
- package/dist/src/monitor/index.js +665 -171
- package/dist/src/monitor/index.js.map +1 -1
- package/dist/src/monitor/media.js +165 -6
- package/dist/src/monitor/media.js.map +1 -1
- package/dist/src/monitor/nudge-runner.js +232 -0
- package/dist/src/monitor/nudge-runner.js.map +1 -0
- package/dist/src/monitor/nudge-state.js +58 -0
- package/dist/src/monitor/nudge-state.js.map +1 -0
- package/dist/src/monitor/owner-reply-persistence.js +92 -0
- package/dist/src/monitor/owner-reply-persistence.js.map +1 -0
- package/dist/src/monitor/pending-nudge-persistence.js +15 -0
- package/dist/src/monitor/pending-nudge-persistence.js.map +1 -0
- package/dist/src/monitor/settings-sync.js +28 -0
- package/dist/src/monitor/settings-sync.js.map +1 -0
- package/dist/src/monitor/utils.js +0 -4
- package/dist/src/monitor/utils.js.map +1 -1
- package/dist/src/nudge-candidate.js +109 -0
- package/dist/src/nudge-candidate.js.map +1 -0
- package/dist/src/nudge-decision.js +309 -0
- package/dist/src/nudge-decision.js.map +1 -0
- package/dist/src/nudge-messages.js +25 -0
- package/dist/src/nudge-messages.js.map +1 -0
- package/dist/src/nudge-scheduler.js +91 -0
- package/dist/src/nudge-scheduler.js.map +1 -0
- package/dist/src/pending-nudge.js +57 -0
- package/dist/src/pending-nudge.js.map +1 -0
- package/dist/src/settings.js +77 -5
- package/dist/src/settings.js.map +1 -1
- package/dist/src/setup-core.js +164 -0
- package/dist/src/setup-core.js.map +1 -0
- package/dist/src/setup-surface.js +85 -0
- package/dist/src/setup-surface.js.map +1 -0
- package/dist/src/telemetry.js +252 -0
- package/dist/src/telemetry.js.map +1 -0
- package/dist/src/tlon-binary.js +46 -0
- package/dist/src/tlon-binary.js.map +1 -0
- package/dist/src/tlon-tool-guard.js +44 -0
- package/dist/src/tlon-tool-guard.js.map +1 -0
- package/dist/src/tool-trace.js +100 -0
- package/dist/src/tool-trace.js.map +1 -0
- package/dist/src/types.js +31 -1
- package/dist/src/types.js.map +1 -1
- package/dist/src/urbit/api-client.js +4 -3
- package/dist/src/urbit/api-client.js.map +1 -1
- package/dist/src/urbit/base-url.js +2 -2
- package/dist/src/urbit/base-url.js.map +1 -1
- package/dist/src/urbit/fetch.js +1 -1
- package/dist/src/urbit/fetch.js.map +1 -1
- package/dist/src/urbit/upload.js +1 -1
- package/dist/src/urbit/upload.js.map +1 -1
- package/dist/src/version.generated.js +1 -1
- package/dist/src/version.generated.js.map +1 -1
- package/package.json +34 -24
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure decisioning helpers for the re-engagement nudge scheduler.
|
|
3
|
+
*
|
|
4
|
+
* All functions here are deterministic and do not perform I/O. The
|
|
5
|
+
* scheduler uses them to compute whether a tick should send anything.
|
|
6
|
+
*/
|
|
7
|
+
export const DEFAULT_ACTIVE_HOURS_START = "09:00";
|
|
8
|
+
export const DEFAULT_ACTIVE_HOURS_END = "21:00";
|
|
9
|
+
export const DEFAULT_ACTIVE_HOURS_TIMEZONE = "America/New_York";
|
|
10
|
+
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
|
11
|
+
/** Whole 24-hour days between two absolute instants. */
|
|
12
|
+
export function daysBetween(earlierMs, laterMs) {
|
|
13
|
+
if (!Number.isFinite(earlierMs) || !Number.isFinite(laterMs)) {
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
16
|
+
const diff = laterMs - earlierMs;
|
|
17
|
+
if (diff <= 0) {
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
return Math.floor(diff / ONE_DAY_MS);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Map days-idle to the stage we'd like to have sent by now.
|
|
24
|
+
*
|
|
25
|
+
* Returns `null` when no nudge is wanted at this idle level.
|
|
26
|
+
*/
|
|
27
|
+
export function computeTargetStage(daysIdle) {
|
|
28
|
+
if (daysIdle < 7) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
if (daysIdle < 14) {
|
|
32
|
+
return 1;
|
|
33
|
+
}
|
|
34
|
+
if (daysIdle < 30) {
|
|
35
|
+
return 2;
|
|
36
|
+
}
|
|
37
|
+
return 3;
|
|
38
|
+
}
|
|
39
|
+
const ACTIVE_HOURS_TIME_PATTERN = /^(?:([01]\d|2[0-3]):([0-5]\d)|24:00)$/;
|
|
40
|
+
function resolveUserTimezone(cfg) {
|
|
41
|
+
const configured = cfg?.agents?.defaults?.userTimezone;
|
|
42
|
+
if (typeof configured === "string") {
|
|
43
|
+
const trimmed = configured.trim();
|
|
44
|
+
if (trimmed) {
|
|
45
|
+
try {
|
|
46
|
+
new Intl.DateTimeFormat("en-US", { timeZone: trimmed }).format(new Date());
|
|
47
|
+
return trimmed;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Fall through to the host timezone below.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone?.trim() || "UTC";
|
|
55
|
+
}
|
|
56
|
+
function resolveActiveHoursTimezone(cfg, raw) {
|
|
57
|
+
const trimmed = typeof raw === "string" ? raw.trim() : "";
|
|
58
|
+
if (!trimmed || trimmed === "user") {
|
|
59
|
+
return resolveUserTimezone(cfg);
|
|
60
|
+
}
|
|
61
|
+
if (trimmed === "local") {
|
|
62
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone?.trim() || "UTC";
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
new Intl.DateTimeFormat("en-US", { timeZone: trimmed }).format(new Date());
|
|
66
|
+
return trimmed;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return resolveUserTimezone(cfg);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function parseActiveHoursTime(raw, opts) {
|
|
73
|
+
if (typeof raw !== "string") {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const trimmed = raw.trim();
|
|
77
|
+
if (!ACTIVE_HOURS_TIME_PATTERN.test(trimmed)) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const [hourStr, minuteStr] = trimmed.split(":");
|
|
81
|
+
const hour = Number(hourStr);
|
|
82
|
+
const minute = Number(minuteStr);
|
|
83
|
+
if (!Number.isFinite(hour) || !Number.isFinite(minute)) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
if (hour === 24) {
|
|
87
|
+
if (!opts.allow24 || minute !== 0) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
return 24 * 60;
|
|
91
|
+
}
|
|
92
|
+
return hour * 60 + minute;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Return a trimmed `HH:MM` time string from `raw` if it parses against
|
|
96
|
+
* `parseActiveHoursTime`. Returns `null` when `raw` is unset, empty, or
|
|
97
|
+
* malformed. Callers use the `null` case to fall through to a lower-
|
|
98
|
+
* precedence tier's value for that field.
|
|
99
|
+
*/
|
|
100
|
+
function overlayActiveHoursTime(raw, opts) {
|
|
101
|
+
if (typeof raw !== "string") {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
const trimmed = raw.trim();
|
|
105
|
+
if (!trimmed) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
if (parseActiveHoursTime(trimmed, opts) == null) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return trimmed;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Return an IANA timezone from `raw` when the user has explicitly set
|
|
115
|
+
* the `nudgeActiveHoursTimezone` key to a valid zone or keyword
|
|
116
|
+
* (`"user"` / `"local"`). Returns `null` when unset or malformed — the
|
|
117
|
+
* caller falls through to the baseline timezone in that case.
|
|
118
|
+
*/
|
|
119
|
+
function overlayActiveHoursTimezone(cfg, raw) {
|
|
120
|
+
if (typeof raw !== "string") {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const trimmed = raw.trim();
|
|
124
|
+
if (!trimmed) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
if (trimmed === "user") {
|
|
128
|
+
return resolveUserTimezone(cfg);
|
|
129
|
+
}
|
|
130
|
+
if (trimmed === "local") {
|
|
131
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone?.trim() || null;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
new Intl.DateTimeFormat("en-US", { timeZone: trimmed }).format(new Date());
|
|
135
|
+
return trimmed;
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function activeHoursFromShape(cfg, raw) {
|
|
142
|
+
if (!raw) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
const start = typeof raw.start === "string" ? raw.start.trim() : undefined;
|
|
146
|
+
const end = typeof raw.end === "string" ? raw.end.trim() : undefined;
|
|
147
|
+
if (!start || !end) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
if (parseActiveHoursTime(start, { allow24: false }) == null ||
|
|
151
|
+
parseActiveHoursTime(end, { allow24: true }) == null) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
start,
|
|
156
|
+
end,
|
|
157
|
+
timezone: resolveActiveHoursTimezone(cfg, raw.timezone),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function activeHoursFromChannelsTlon(cfg) {
|
|
161
|
+
const tlon = cfg?.channels?.tlon;
|
|
162
|
+
const raw = tlon?.nudgeActiveHours;
|
|
163
|
+
if (!raw || typeof raw !== "object") {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
return activeHoursFromShape(cfg, raw);
|
|
167
|
+
}
|
|
168
|
+
function activeHoursFromFileConfig(cfg) {
|
|
169
|
+
const agentsDefaults = cfg?.agents?.defaults?.heartbeat?.activeHours;
|
|
170
|
+
if (!agentsDefaults || typeof agentsDefaults !== "object") {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
return activeHoursFromShape(cfg, agentsDefaults);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Four-tier fallback for active hours, applied as a field-wise overlay:
|
|
177
|
+
* 1. Settings store keys (`nudgeActiveHours*`) — runtime-tunable via
|
|
178
|
+
* %settings. Each of `start` / `end` / `timezone` is overlaid
|
|
179
|
+
* individually over the baseline below; a partial settings edit
|
|
180
|
+
* (e.g. timezone-only, or single bound) takes effect without
|
|
181
|
+
* forcing operators to rewrite the full triple.
|
|
182
|
+
* 2. Plugin file config at `cfg.channels.tlon.nudgeActiveHours` — a
|
|
183
|
+
* first-class static override for deployments that want the plugin
|
|
184
|
+
* scheduler to run on a fixed window without reusing the legacy
|
|
185
|
+
* heartbeat block.
|
|
186
|
+
* 3. Backwards-compat at `cfg.agents.defaults.heartbeat.activeHours` —
|
|
187
|
+
* preserves behavior for tlawn.py-generated hosted configs that set
|
|
188
|
+
* the legacy heartbeat block.
|
|
189
|
+
* 4. Hard-coded defaults (`09:00`–`21:00`, `America/New_York`).
|
|
190
|
+
*
|
|
191
|
+
* Malformed settings values (bad `HH:MM`, unknown timezone) fall
|
|
192
|
+
* through to the baseline for that specific field, so a broken
|
|
193
|
+
* individual key cannot promote a broken settings tier over a valid
|
|
194
|
+
* lower-precedence configuration.
|
|
195
|
+
*/
|
|
196
|
+
export function resolveActiveHours(settings, cfg) {
|
|
197
|
+
const baseline = activeHoursFromChannelsTlon(cfg) ??
|
|
198
|
+
activeHoursFromFileConfig(cfg) ?? {
|
|
199
|
+
start: DEFAULT_ACTIVE_HOURS_START,
|
|
200
|
+
end: DEFAULT_ACTIVE_HOURS_END,
|
|
201
|
+
timezone: DEFAULT_ACTIVE_HOURS_TIMEZONE,
|
|
202
|
+
};
|
|
203
|
+
const startOverride = overlayActiveHoursTime(settings.nudgeActiveHoursStart, { allow24: false });
|
|
204
|
+
const endOverride = overlayActiveHoursTime(settings.nudgeActiveHoursEnd, {
|
|
205
|
+
allow24: true,
|
|
206
|
+
});
|
|
207
|
+
const timezoneOverride = overlayActiveHoursTimezone(cfg, settings.nudgeActiveHoursTimezone);
|
|
208
|
+
return {
|
|
209
|
+
start: startOverride ?? baseline.start,
|
|
210
|
+
end: endOverride ?? baseline.end,
|
|
211
|
+
timezone: timezoneOverride ?? baseline.timezone,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Derive "HH:MM" for `date` in the given IANA timezone.
|
|
216
|
+
*/
|
|
217
|
+
function formatLocalHhMm(date, timezone) {
|
|
218
|
+
const fmt = new Intl.DateTimeFormat("en-GB", {
|
|
219
|
+
timeZone: timezone,
|
|
220
|
+
hour: "2-digit",
|
|
221
|
+
minute: "2-digit",
|
|
222
|
+
hour12: false,
|
|
223
|
+
});
|
|
224
|
+
const parts = fmt.formatToParts(date);
|
|
225
|
+
let hh = "00";
|
|
226
|
+
let mm = "00";
|
|
227
|
+
for (const part of parts) {
|
|
228
|
+
if (part.type === "hour") {
|
|
229
|
+
hh = part.value;
|
|
230
|
+
}
|
|
231
|
+
if (part.type === "minute") {
|
|
232
|
+
mm = part.value;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// `Intl.DateTimeFormat` with `hour12: false` can yield "24:00" at midnight
|
|
236
|
+
// on some engines; normalize to "00:00" for comparison consistency.
|
|
237
|
+
if (hh === "24") {
|
|
238
|
+
hh = "00";
|
|
239
|
+
}
|
|
240
|
+
return `${hh}:${mm}`;
|
|
241
|
+
}
|
|
242
|
+
function resolveMinutesInTimeZone(date, timezone) {
|
|
243
|
+
const local = formatLocalHhMm(date, timezone);
|
|
244
|
+
const [hourStr, minuteStr] = local.split(":");
|
|
245
|
+
const hour = Number(hourStr);
|
|
246
|
+
const minute = Number(minuteStr);
|
|
247
|
+
if (!Number.isFinite(hour) || !Number.isFinite(minute)) {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
return hour * 60 + minute;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* `true` when `date` falls inside `[start, end)` in the configured timezone.
|
|
254
|
+
* Supports wrap-around windows (e.g. `22:00`–`06:00`).
|
|
255
|
+
*/
|
|
256
|
+
export function inActiveHours(date, activeHours) {
|
|
257
|
+
const startMin = parseActiveHoursTime(activeHours.start, { allow24: false });
|
|
258
|
+
const endMin = parseActiveHoursTime(activeHours.end, { allow24: true });
|
|
259
|
+
if (startMin == null || endMin == null) {
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
if (startMin === endMin) {
|
|
263
|
+
// Match OpenClaw heartbeat semantics: equal bounds form a zero-width
|
|
264
|
+
// window, so scheduled work is always outside the active window.
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
const now = resolveMinutesInTimeZone(date, activeHours.timezone);
|
|
268
|
+
if (now == null) {
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
if (endMin > startMin) {
|
|
272
|
+
return now >= startMin && now < endMin;
|
|
273
|
+
}
|
|
274
|
+
// Wrap-around: e.g. 22:00 – 06:00.
|
|
275
|
+
return now >= startMin || now < endMin;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Resolve the most authoritative last-owner-activity timestamp (epoch ms).
|
|
279
|
+
*
|
|
280
|
+
* Prefers the synchronous in-memory shadow over the settings mirror, since
|
|
281
|
+
* the shadow is updated the moment the owner-reply handler observes a
|
|
282
|
+
* message and cannot lag behind a subscription-delivered clear.
|
|
283
|
+
*/
|
|
284
|
+
export function resolveLastOwnerInstant(shadow, settings) {
|
|
285
|
+
if (shadow && typeof shadow.at === "number" && Number.isFinite(shadow.at)) {
|
|
286
|
+
return shadow.at;
|
|
287
|
+
}
|
|
288
|
+
if (typeof settings.lastOwnerMessageAt === "number") {
|
|
289
|
+
return settings.lastOwnerMessageAt;
|
|
290
|
+
}
|
|
291
|
+
if (typeof settings.lastOwnerMessageDate === "string") {
|
|
292
|
+
const parsed = Date.parse(settings.lastOwnerMessageDate);
|
|
293
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
294
|
+
}
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
export function shouldSend(inputs) {
|
|
298
|
+
if (inputs.targetStage == null) {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
if (!inputs.ownerShip) {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
if (!inputs.isInActiveHours) {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
return inputs.targetStage > inputs.lastNudgeStage;
|
|
308
|
+
}
|
|
309
|
+
//# sourceMappingURL=nudge-decision.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nudge-decision.js","sourceRoot":"","sources":["../../src/nudge-decision.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical re-engagement nudge messages, keyed by stage.
|
|
3
|
+
*
|
|
4
|
+
* Moved from the LLM-owned prompt (`tlonbot/prompts/HEARTBEAT.md`) into the
|
|
5
|
+
* plugin so the plugin is the single source of truth for nudge content.
|
|
6
|
+
*/
|
|
7
|
+
export const NUDGE_MESSAGES = {
|
|
8
|
+
1: "Hey! Quick ideas for your week:\n" +
|
|
9
|
+
'• "Make me a group about cooking" — I\'ll set it up, then give you a link to invite your friends\n' +
|
|
10
|
+
'• "Tell me the weather every morning at 8am"\n' +
|
|
11
|
+
'• "Send me a daily digest with breaking news about AI"\n\n' +
|
|
12
|
+
"Just reply with any of these or ask me anything 🌱",
|
|
13
|
+
2: "A few things I can do for you:\n" +
|
|
14
|
+
"• Create a group — tell me what you're into and I'll set it up. Invite your friends and get a conversation going.\n" +
|
|
15
|
+
'• Run recurring jobs — "track AAPL and tell me if it moves more than 5%", "summarize the news every morning", "help me track my meals"\n' +
|
|
16
|
+
"• Watch a channel and ping you when something important comes up\n\n" +
|
|
17
|
+
"What sounds useful?",
|
|
18
|
+
3: "Still here! Here's what I can do:\n\n" +
|
|
19
|
+
"**Groups** — I'll create groups for you, help brainstorm ideas, and manage permissions. Invite your friends and hang out.\n" +
|
|
20
|
+
"**Recurring jobs** — daily weather, news alerts, meal tracking, stock tracking, scheduled reminders — just tell me what and when.\n" +
|
|
21
|
+
"**Catch up** — summarize threads, watch channels for keywords\n" +
|
|
22
|
+
"**Research** — web search, fact-check, find recipes/flights/etc.\n\n" +
|
|
23
|
+
"Just say the word 🌱",
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=nudge-messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nudge-messages.js","sourceRoot":"","sources":["../../src/nudge-messages.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,CAAC,MAAM,cAAc,GAA+B;IACxD,CAAC,EACC,mCAAmC;QACnC,oGAAoG;QACpG,gDAAgD;QAChD,4DAA4D;QAC5D,oDAAoD;IACtD,CAAC,EACC,kCAAkC;QAClC,qHAAqH;QACrH,0IAA0I;QAC1I,sEAAsE;QACtE,qBAAqB;IACvB,CAAC,EACC,uCAAuC;QACvC,6HAA6H;QAC7H,qIAAqI;QACrI,iEAAiE;QACjE,sEAAsE;QACtE,sBAAsB;CACzB,CAAC"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interval-driven scheduler for re-engagement nudge ticks.
|
|
3
|
+
*
|
|
4
|
+
* Owns the timer lifecycle, reentrancy guard, and abort handling. Knows
|
|
5
|
+
* nothing about Urbit, settings, or telemetry — the caller supplies a
|
|
6
|
+
* tick callback that does the evaluation and side effects.
|
|
7
|
+
*/
|
|
8
|
+
export const DEFAULT_NUDGE_TICK_INTERVAL_MS = 15 * 60 * 1000;
|
|
9
|
+
export function createNudgeScheduler(opts) {
|
|
10
|
+
const intervalMs = opts.intervalMs ?? DEFAULT_NUDGE_TICK_INTERVAL_MS;
|
|
11
|
+
let timer = null;
|
|
12
|
+
let running = false;
|
|
13
|
+
let started = false;
|
|
14
|
+
// Tracked so `stop()` can await an in-flight tick before the caller
|
|
15
|
+
// tears down the rest of the monitor (persistence queues, `api.close()`
|
|
16
|
+
// etc.). Cleared inside the tick's `finally` once the body resolves.
|
|
17
|
+
let activeTick = null;
|
|
18
|
+
async function runTick() {
|
|
19
|
+
if (opts.abortSignal?.aborted) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (running) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
running = true;
|
|
26
|
+
const task = (async () => {
|
|
27
|
+
try {
|
|
28
|
+
await opts.tick();
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
opts.error?.(`[tlon] nudge tick failed: ${String(err)}`);
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
running = false;
|
|
35
|
+
}
|
|
36
|
+
})();
|
|
37
|
+
activeTick = task;
|
|
38
|
+
try {
|
|
39
|
+
await task;
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
if (activeTick === task) {
|
|
43
|
+
activeTick = null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
start() {
|
|
49
|
+
if (started) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (opts.abortSignal?.aborted) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
started = true;
|
|
56
|
+
// Schedule the first tick on the next macrotask so callers can finish
|
|
57
|
+
// their synchronous startup wiring before the tick observes state.
|
|
58
|
+
setTimeout(() => {
|
|
59
|
+
if (opts.abortSignal?.aborted || !started) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
void runTick();
|
|
63
|
+
}, 0);
|
|
64
|
+
timer = setInterval(() => {
|
|
65
|
+
if (opts.abortSignal?.aborted) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
void runTick();
|
|
69
|
+
}, intervalMs);
|
|
70
|
+
},
|
|
71
|
+
async stop() {
|
|
72
|
+
started = false;
|
|
73
|
+
if (timer != null) {
|
|
74
|
+
clearInterval(timer);
|
|
75
|
+
timer = null;
|
|
76
|
+
}
|
|
77
|
+
// Drain any in-flight tick so shutdown-time cleanup (queue flush,
|
|
78
|
+
// shadow clear, api.close) runs after the tick's last writes land.
|
|
79
|
+
if (activeTick) {
|
|
80
|
+
await activeTick.catch(() => undefined);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
async tickNow() {
|
|
84
|
+
await runTick();
|
|
85
|
+
},
|
|
86
|
+
get isRunning() {
|
|
87
|
+
return running;
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=nudge-scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nudge-scheduler.js","sourceRoot":"","sources":["../../src/nudge-scheduler.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,8BAA8B,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAsB7D,MAAM,UAAU,oBAAoB,CAAC,IAA2B;IAC9D,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,8BAA8B,CAAC;IACrE,IAAI,KAAK,GAA0B,IAAI,CAAC;IACxC,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,oEAAoE;IACpE,wEAAwE;IACxE,qEAAqE;IACrE,IAAI,UAAU,GAAyB,IAAI,CAAC;IAE5C,KAAK,UAAU,OAAO;QACpB,IAAI,IAAI,CAAC,WAAW,EAAE,OAAO,EAAE,CAAC;YAAA,OAAO;QAAA,CAAC;QACxC,IAAI,OAAO,EAAE,CAAC;YAAA,OAAO;QAAA,CAAC;QACtB,OAAO,GAAG,IAAI,CAAC;QACf,MAAM,IAAI,GAAG,CAAC,KAAK,IAAI,EAAE;YACvB,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;YACpB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,CAAC,KAAK,EAAE,CAAC,6BAA6B,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC3D,CAAC;oBAAS,CAAC;gBACT,OAAO,GAAG,KAAK,CAAC;YAClB,CAAC;QACH,CAAC,CAAC,EAAE,CAAC;QACL,UAAU,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC;YACH,MAAM,IAAI,CAAC;QACb,CAAC;gBAAS,CAAC;YACT,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;gBACxB,UAAU,GAAG,IAAI,CAAC;YACpB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,KAAK;YACH,IAAI,OAAO,EAAE,CAAC;gBAAA,OAAO;YAAA,CAAC;YACtB,IAAI,IAAI,CAAC,WAAW,EAAE,OAAO,EAAE,CAAC;gBAAA,OAAO;YAAA,CAAC;YACxC,OAAO,GAAG,IAAI,CAAC;YACf,sEAAsE;YACtE,mEAAmE;YACnE,UAAU,CAAC,GAAG,EAAE;gBACd,IAAI,IAAI,CAAC,WAAW,EAAE,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;oBAAA,OAAO;gBAAA,CAAC;gBACpD,KAAK,OAAO,EAAE,CAAC;YACjB,CAAC,EAAE,CAAC,CAAC,CAAC;YACN,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;gBACvB,IAAI,IAAI,CAAC,WAAW,EAAE,OAAO,EAAE,CAAC;oBAAA,OAAO;gBAAA,CAAC;gBACxC,KAAK,OAAO,EAAE,CAAC;YACjB,CAAC,EAAE,UAAU,CAAC,CAAC;QACjB,CAAC;QACD,KAAK,CAAC,IAAI;YACR,OAAO,GAAG,KAAK,CAAC;YAChB,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;gBAClB,aAAa,CAAC,KAAK,CAAC,CAAC;gBACrB,KAAK,GAAG,IAAI,CAAC;YACf,CAAC;YACD,kEAAkE;YAClE,mEAAmE;YACnE,IAAI,UAAU,EAAE,CAAC;gBACf,MAAM,UAAU,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YAC1C,CAAC;QACH,CAAC;QACD,KAAK,CAAC,OAAO;YACX,MAAM,OAAO,EAAE,CAAC;QAClB,CAAC;QACD,IAAI,SAAS;YACX,OAAO,OAAO,CAAC;QACjB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pending nudge shared state for heartbeat re-engagement attribution.
|
|
3
|
+
*
|
|
4
|
+
* When the plugin-driven scheduler sends a re-engagement nudge, a
|
|
5
|
+
* PendingNudge record is stored here. If the owner replies within the
|
|
6
|
+
* attribution window, the monitor emits a re-engagement event, injects the
|
|
7
|
+
* nudge content as context for the agent, and clears the record.
|
|
8
|
+
*
|
|
9
|
+
* Keyed by accountId — each account has at most one pending nudge at a
|
|
10
|
+
* time. Per-account persist callbacks route writes to the correct ship's
|
|
11
|
+
* settings store.
|
|
12
|
+
*/
|
|
13
|
+
/** Default re-engagement attribution window: 72 hours */
|
|
14
|
+
export const DEFAULT_ATTRIBUTION_WINDOW_MS = 72 * 60 * 60 * 1000;
|
|
15
|
+
const pendingNudges = new Map();
|
|
16
|
+
const persistCallbacks = new Map();
|
|
17
|
+
export function setPendingNudge(accountId, nudge) {
|
|
18
|
+
pendingNudges.set(accountId, nudge);
|
|
19
|
+
persistCallbacks.get(accountId)?.(nudge);
|
|
20
|
+
}
|
|
21
|
+
export function getPendingNudge(accountId) {
|
|
22
|
+
return pendingNudges.get(accountId) ?? null;
|
|
23
|
+
}
|
|
24
|
+
export function clearPendingNudge(accountId) {
|
|
25
|
+
pendingNudges.delete(accountId);
|
|
26
|
+
persistCallbacks.get(accountId)?.(null);
|
|
27
|
+
}
|
|
28
|
+
export function registerPersistCallback(accountId, cb) {
|
|
29
|
+
persistCallbacks.set(accountId, cb);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Sync from settings store (startup load or hot-reload).
|
|
33
|
+
* Does NOT trigger persist callback — the data came from the store.
|
|
34
|
+
*/
|
|
35
|
+
export function syncPendingNudgeFromStore(accountId, nudge) {
|
|
36
|
+
if (nudge) {
|
|
37
|
+
pendingNudges.set(accountId, nudge);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
pendingNudges.delete(accountId);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Returns true if the pending nudge is still within the attribution window.
|
|
45
|
+
* @param nowMs - current time in epoch ms (injectable for tests)
|
|
46
|
+
* @param windowMs - attribution window in ms (overridable, defaults to 72h)
|
|
47
|
+
*/
|
|
48
|
+
export function isNudgeEligible(nudge, nowMs = Date.now(), windowMs = DEFAULT_ATTRIBUTION_WINDOW_MS) {
|
|
49
|
+
return nowMs - nudge.sentAt <= windowMs;
|
|
50
|
+
}
|
|
51
|
+
export const _testing = {
|
|
52
|
+
clearAll: () => {
|
|
53
|
+
pendingNudges.clear();
|
|
54
|
+
persistCallbacks.clear();
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=pending-nudge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-nudge.js","sourceRoot":"","sources":["../../src/pending-nudge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAWH,yDAAyD;AACzD,MAAM,CAAC,MAAM,6BAA6B,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEjE,MAAM,aAAa,GAAG,IAAI,GAAG,EAAwB,CAAC;AACtD,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAgD,CAAC;AAEjF,MAAM,UAAU,eAAe,CAAC,SAAiB,EAAE,KAAmB;IACpE,aAAa,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACpC,gBAAgB,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC/C,OAAO,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,aAAa,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAChC,gBAAgB,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,uBAAuB,CACrC,SAAiB,EACjB,EAAwC;IAExC,gBAAgB,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,SAAiB,EAAE,KAA0B;IACrF,IAAI,KAAK,EAAE,CAAC;QACV,aAAa,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACtC,CAAC;SAAM,CAAC;QACN,aAAa,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAClC,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,KAAmB,EACnB,QAAgB,IAAI,CAAC,GAAG,EAAE,EAC1B,WAAmB,6BAA6B;IAEhD,OAAO,KAAK,GAAG,KAAK,CAAC,MAAM,IAAI,QAAQ,CAAC;AAC1C,CAAC;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,QAAQ,EAAE,GAAG,EAAE;QACb,aAAa,CAAC,KAAK,EAAE,CAAC;QACtB,gBAAgB,CAAC,KAAK,EAAE,CAAC;IAC3B,CAAC;CACF,CAAC"}
|
package/dist/src/settings.js
CHANGED
|
@@ -36,6 +36,54 @@ function parseChannelRules(value) {
|
|
|
36
36
|
}
|
|
37
37
|
return undefined;
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Parse pendingNudge — handles both JSON string and object formats.
|
|
41
|
+
* Settings-store stores complex objects as JSON strings.
|
|
42
|
+
*/
|
|
43
|
+
function parsePendingNudge(value) {
|
|
44
|
+
if (!value) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
let parsed = value;
|
|
48
|
+
if (typeof value === "string") {
|
|
49
|
+
try {
|
|
50
|
+
parsed = JSON.parse(value);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!parsed || typeof parsed !== "object") {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
const obj = parsed;
|
|
60
|
+
if (typeof obj.sentAt !== "number" ||
|
|
61
|
+
typeof obj.ownerShip !== "string" ||
|
|
62
|
+
typeof obj.accountId !== "string" ||
|
|
63
|
+
!(obj.stage === 1 || obj.stage === 2 || obj.stage === 3)) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
const base = {
|
|
67
|
+
sentAt: obj.sentAt,
|
|
68
|
+
stage: obj.stage,
|
|
69
|
+
ownerShip: obj.ownerShip,
|
|
70
|
+
accountId: obj.accountId,
|
|
71
|
+
};
|
|
72
|
+
if (typeof obj.content === "string") {
|
|
73
|
+
base.content = obj.content;
|
|
74
|
+
}
|
|
75
|
+
return base;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Parse lastNudgeStage — accepts number or numeric string, must be 1, 2, or 3.
|
|
79
|
+
*/
|
|
80
|
+
function parseLastNudgeStage(value) {
|
|
81
|
+
const num = typeof value === "string" ? Number(value) : value;
|
|
82
|
+
if (num === 1 || num === 2 || num === 3) {
|
|
83
|
+
return num;
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
39
87
|
/**
|
|
40
88
|
* Parse settings from the raw Urbit settings-store response.
|
|
41
89
|
* The response shape is: { [bucket]: { [key]: value } }
|
|
@@ -74,6 +122,15 @@ export function parseSettingsResponse(raw) {
|
|
|
74
122
|
pendingApprovals: parsePendingApprovals(settings.pendingApprovals),
|
|
75
123
|
lastOwnerMessageAt: typeof settings.lastOwnerMessageAt === "number" ? settings.lastOwnerMessageAt : undefined,
|
|
76
124
|
lastOwnerMessageDate: typeof settings.lastOwnerMessageDate === "string" ? settings.lastOwnerMessageDate : undefined,
|
|
125
|
+
pendingNudge: parsePendingNudge(settings.pendingNudge),
|
|
126
|
+
lastNudgeStage: parseLastNudgeStage(settings.lastNudgeStage),
|
|
127
|
+
nudgeActiveHoursStart: typeof settings.nudgeActiveHoursStart === "string"
|
|
128
|
+
? settings.nudgeActiveHoursStart
|
|
129
|
+
: undefined,
|
|
130
|
+
nudgeActiveHoursEnd: typeof settings.nudgeActiveHoursEnd === "string" ? settings.nudgeActiveHoursEnd : undefined,
|
|
131
|
+
nudgeActiveHoursTimezone: typeof settings.nudgeActiveHoursTimezone === "string"
|
|
132
|
+
? settings.nudgeActiveHoursTimezone
|
|
133
|
+
: undefined,
|
|
77
134
|
};
|
|
78
135
|
}
|
|
79
136
|
function isChannelRulesObject(val) {
|
|
@@ -210,6 +267,21 @@ export function applySettingsUpdate(current, key, value) {
|
|
|
210
267
|
case "lastOwnerMessageDate":
|
|
211
268
|
next.lastOwnerMessageDate = typeof value === "string" ? value : undefined;
|
|
212
269
|
break;
|
|
270
|
+
case "pendingNudge":
|
|
271
|
+
next.pendingNudge = parsePendingNudge(value);
|
|
272
|
+
break;
|
|
273
|
+
case "lastNudgeStage":
|
|
274
|
+
next.lastNudgeStage = parseLastNudgeStage(value);
|
|
275
|
+
break;
|
|
276
|
+
case "nudgeActiveHoursStart":
|
|
277
|
+
next.nudgeActiveHoursStart = typeof value === "string" ? value : undefined;
|
|
278
|
+
break;
|
|
279
|
+
case "nudgeActiveHoursEnd":
|
|
280
|
+
next.nudgeActiveHoursEnd = typeof value === "string" ? value : undefined;
|
|
281
|
+
break;
|
|
282
|
+
case "nudgeActiveHoursTimezone":
|
|
283
|
+
next.nudgeActiveHoursTimezone = typeof value === "string" ? value : undefined;
|
|
284
|
+
break;
|
|
213
285
|
}
|
|
214
286
|
return next;
|
|
215
287
|
}
|
|
@@ -262,14 +334,14 @@ export function createSettingsManager(api, logger) {
|
|
|
262
334
|
state.current = parseSettingsResponse(deskData ?? {});
|
|
263
335
|
state.loaded = true;
|
|
264
336
|
logger?.log?.(`[settings] Loaded: ${JSON.stringify(state.current)}`);
|
|
265
|
-
return state.current;
|
|
337
|
+
return { settings: state.current, fresh: true };
|
|
266
338
|
}
|
|
267
339
|
catch (err) {
|
|
268
|
-
//
|
|
269
|
-
|
|
270
|
-
|
|
340
|
+
// Preserve the last good snapshot on scry failure so refresh fallback
|
|
341
|
+
// does not transiently clobber live runtime state with an empty object.
|
|
342
|
+
logger?.log?.(`[settings] Load failed (keeping previous settings): ${String(err)}`);
|
|
271
343
|
state.loaded = true;
|
|
272
|
-
return state.current;
|
|
344
|
+
return { settings: state.current, fresh: false };
|
|
273
345
|
}
|
|
274
346
|
},
|
|
275
347
|
/**
|