@tloncorp/openclaw 0.1.0 → 0.4.0

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.
Files changed (88) hide show
  1. package/README.md +232 -23
  2. package/dist/index.js +292 -43
  3. package/dist/index.js.map +1 -1
  4. package/dist/setup-api.js +3 -0
  5. package/dist/setup-api.js.map +1 -0
  6. package/dist/setup-entry.js +4 -0
  7. package/dist/setup-entry.js.map +1 -0
  8. package/dist/src/actions.js +30 -8
  9. package/dist/src/actions.js.map +1 -1
  10. package/dist/src/channel.js +156 -373
  11. package/dist/src/channel.js.map +1 -1
  12. package/dist/src/channel.runtime.js +142 -0
  13. package/dist/src/channel.runtime.js.map +1 -0
  14. package/dist/src/config-schema.js +41 -1
  15. package/dist/src/config-schema.js.map +1 -1
  16. package/dist/src/effective-owner.js +22 -0
  17. package/dist/src/effective-owner.js.map +1 -0
  18. package/dist/src/gateway-status.js +72 -0
  19. package/dist/src/gateway-status.js.map +1 -0
  20. package/dist/src/monitor/approval.js +194 -96
  21. package/dist/src/monitor/approval.js.map +1 -1
  22. package/dist/src/monitor/command-auth.js +62 -0
  23. package/dist/src/monitor/command-auth.js.map +1 -0
  24. package/dist/src/monitor/command-bridge.js +27 -0
  25. package/dist/src/monitor/command-bridge.js.map +1 -0
  26. package/dist/src/monitor/computing-presence.js +221 -0
  27. package/dist/src/monitor/computing-presence.js.map +1 -0
  28. package/dist/src/monitor/discovery.js +17 -9
  29. package/dist/src/monitor/discovery.js.map +1 -1
  30. package/dist/src/monitor/index.js +960 -251
  31. package/dist/src/monitor/index.js.map +1 -1
  32. package/dist/src/monitor/media.js +195 -30
  33. package/dist/src/monitor/media.js.map +1 -1
  34. package/dist/src/monitor/nudge-runner.js +232 -0
  35. package/dist/src/monitor/nudge-runner.js.map +1 -0
  36. package/dist/src/monitor/nudge-state.js +58 -0
  37. package/dist/src/monitor/nudge-state.js.map +1 -0
  38. package/dist/src/monitor/owner-reply-persistence.js +92 -0
  39. package/dist/src/monitor/owner-reply-persistence.js.map +1 -0
  40. package/dist/src/monitor/pending-nudge-persistence.js +15 -0
  41. package/dist/src/monitor/pending-nudge-persistence.js.map +1 -0
  42. package/dist/src/monitor/settings-sync.js +28 -0
  43. package/dist/src/monitor/settings-sync.js.map +1 -0
  44. package/dist/src/monitor/utils.js +21 -4
  45. package/dist/src/monitor/utils.js.map +1 -1
  46. package/dist/src/nudge-decision.js +309 -0
  47. package/dist/src/nudge-decision.js.map +1 -0
  48. package/dist/src/nudge-messages.js +25 -0
  49. package/dist/src/nudge-messages.js.map +1 -0
  50. package/dist/src/nudge-scheduler.js +91 -0
  51. package/dist/src/nudge-scheduler.js.map +1 -0
  52. package/dist/src/pending-nudge.js +57 -0
  53. package/dist/src/pending-nudge.js.map +1 -0
  54. package/dist/src/session-roles.js +39 -0
  55. package/dist/src/session-roles.js.map +1 -0
  56. package/dist/src/settings.js +82 -6
  57. package/dist/src/settings.js.map +1 -1
  58. package/dist/src/setup-core.js +164 -0
  59. package/dist/src/setup-core.js.map +1 -0
  60. package/dist/src/setup-surface.js +85 -0
  61. package/dist/src/setup-surface.js.map +1 -0
  62. package/dist/src/telemetry.js +252 -0
  63. package/dist/src/telemetry.js.map +1 -0
  64. package/dist/src/tlon-binary.js +46 -0
  65. package/dist/src/tlon-binary.js.map +1 -0
  66. package/dist/src/tlon-tool-guard.js +44 -0
  67. package/dist/src/tlon-tool-guard.js.map +1 -0
  68. package/dist/src/tool-trace.js +100 -0
  69. package/dist/src/tool-trace.js.map +1 -0
  70. package/dist/src/types.js +35 -0
  71. package/dist/src/types.js.map +1 -1
  72. package/dist/src/urbit/api-client.js +4 -3
  73. package/dist/src/urbit/api-client.js.map +1 -1
  74. package/dist/src/urbit/base-url.js +2 -2
  75. package/dist/src/urbit/base-url.js.map +1 -1
  76. package/dist/src/urbit/fetch.js +1 -1
  77. package/dist/src/urbit/fetch.js.map +1 -1
  78. package/dist/src/urbit/send.js +6 -2
  79. package/dist/src/urbit/send.js.map +1 -1
  80. package/dist/src/urbit/sse-client.js +13 -2
  81. package/dist/src/urbit/sse-client.js.map +1 -1
  82. package/dist/src/urbit/upload.js +25 -20
  83. package/dist/src/urbit/upload.js.map +1 -1
  84. package/dist/src/version.generated.js +3 -0
  85. package/dist/src/version.generated.js.map +1 -0
  86. package/package.json +32 -25
  87. package/dist/src/onboarding.js +0 -178
  88. package/dist/src/onboarding.js.map +0 -1
@@ -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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Tracks sender roles per session for tool access control.
3
+ * Used by the before_tool_call hook to enforce owner-only restrictions.
4
+ *
5
+ * Note: We use sessionKey (not sessionId) because that's what the
6
+ * before_tool_call hook provides. Cleanup happens via TTL, not session_end
7
+ * (which provides sessionId, not sessionKey).
8
+ */
9
+ const sessionRoles = new Map();
10
+ // TTL for role entries (1 hour - sessions shouldn't last longer)
11
+ const ROLE_TTL_MS = 60 * 60 * 1000;
12
+ export function setSessionRole(sessionKey, role) {
13
+ // Clean up old entries while we're here
14
+ const now = Date.now();
15
+ for (const [key, entry] of sessionRoles) {
16
+ if (now - entry.timestamp > ROLE_TTL_MS) {
17
+ sessionRoles.delete(key);
18
+ }
19
+ }
20
+ sessionRoles.set(sessionKey, { role, timestamp: now });
21
+ }
22
+ export function getSessionRole(sessionKey) {
23
+ const entry = sessionRoles.get(sessionKey);
24
+ if (!entry) {
25
+ return undefined;
26
+ }
27
+ // Check TTL
28
+ if (Date.now() - entry.timestamp > ROLE_TTL_MS) {
29
+ sessionRoles.delete(sessionKey);
30
+ return undefined;
31
+ }
32
+ return entry.role;
33
+ }
34
+ // Exported for testing - allows time manipulation
35
+ export const _testing = {
36
+ clearAll: () => sessionRoles.clear(),
37
+ getRoleTtlMs: () => ROLE_TTL_MS,
38
+ };
39
+ //# sourceMappingURL=session-roles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-roles.js","sourceRoot":"","sources":["../../src/session-roles.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH,MAAM,YAAY,GAAG,IAAI,GAAG,EAAqB,CAAC;AAElD,iEAAiE;AACjE,MAAM,WAAW,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEnC,MAAM,UAAU,cAAc,CAAC,UAAkB,EAAE,IAAgB;IACjE,wCAAwC;IACxC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,YAAY,EAAE,CAAC;QACxC,IAAI,GAAG,GAAG,KAAK,CAAC,SAAS,GAAG,WAAW,EAAE,CAAC;YACxC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,YAAY,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,UAAkB;IAC/C,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAC3C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,YAAY;IACZ,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,GAAG,WAAW,EAAE,CAAC;QAC/C,YAAY,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAChC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC;AACpB,CAAC;AAED,kDAAkD;AAClD,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,QAAQ,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE;IACpC,YAAY,EAAE,GAAG,EAAE,CAAC,WAAW;CAChC,CAAC"}