dot-agents 0.6.0 → 0.7.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/dist/cli/commands/channel.d.ts.map +1 -1
- package/dist/cli/commands/channel.js +70 -2
- package/dist/cli/commands/channel.js.map +1 -1
- package/dist/daemon/api/channels.d.ts +6 -0
- package/dist/daemon/api/channels.d.ts.map +1 -0
- package/dist/daemon/api/channels.js +143 -0
- package/dist/daemon/api/channels.js.map +1 -0
- package/dist/daemon/api/server.d.ts.map +1 -1
- package/dist/daemon/api/server.js +57 -0
- package/dist/daemon/api/server.js.map +1 -1
- package/dist/daemon/daemon.d.ts +28 -0
- package/dist/daemon/daemon.d.ts.map +1 -1
- package/dist/daemon/daemon.js +174 -6
- package/dist/daemon/daemon.js.map +1 -1
- package/dist/daemon/lib/index.d.ts +1 -0
- package/dist/daemon/lib/index.d.ts.map +1 -1
- package/dist/daemon/lib/index.js +1 -0
- package/dist/daemon/lib/index.js.map +1 -1
- package/dist/daemon/lib/safeguards.d.ts +135 -0
- package/dist/daemon/lib/safeguards.d.ts.map +1 -0
- package/dist/daemon/lib/safeguards.js +250 -0
- package/dist/daemon/lib/safeguards.js.map +1 -0
- package/dist/daemon/lib/watcher.d.ts.map +1 -1
- package/dist/daemon/lib/watcher.js +48 -8
- package/dist/daemon/lib/watcher.js.map +1 -1
- package/dist/daemon/web/app.js +433 -0
- package/dist/daemon/web/index.html +68 -0
- package/dist/daemon/web/styles.css +452 -0
- package/dist/lib/environment.d.ts +1 -0
- package/dist/lib/environment.d.ts.map +1 -1
- package/dist/lib/environment.js +14 -0
- package/dist/lib/environment.js.map +1 -1
- package/dist/lib/invoke.d.ts +8 -0
- package/dist/lib/invoke.d.ts.map +1 -1
- package/dist/lib/invoke.js +28 -16
- package/dist/lib/invoke.js.map +1 -1
- package/dist/lib/processor.d.ts +8 -0
- package/dist/lib/processor.d.ts.map +1 -1
- package/dist/lib/processor.js +16 -2
- package/dist/lib/processor.js.map +1 -1
- package/dist/lib/validation/persona.d.ts.map +1 -1
- package/dist/lib/validation/persona.js +41 -4
- package/dist/lib/validation/persona.js.map +1 -1
- package/package.json +15 -9
- package/dist/lib/channel.test.d.ts +0 -2
- package/dist/lib/channel.test.d.ts.map +0 -1
- package/dist/lib/channel.test.js +0 -33
- package/dist/lib/channel.test.js.map +0 -1
- package/dist/lib/frontmatter.test.d.ts +0 -2
- package/dist/lib/frontmatter.test.d.ts.map +0 -1
- package/dist/lib/frontmatter.test.js +0 -60
- package/dist/lib/frontmatter.test.js.map +0 -1
- package/dist/lib/integration.test.d.ts +0 -2
- package/dist/lib/integration.test.d.ts.map +0 -1
- package/dist/lib/integration.test.js +0 -445
- package/dist/lib/integration.test.js.map +0 -1
- package/dist/lib/invoke.test.d.ts +0 -2
- package/dist/lib/invoke.test.d.ts.map +0 -1
- package/dist/lib/invoke.test.js +0 -82
- package/dist/lib/invoke.test.js.map +0 -1
- package/dist/lib/persona.test.d.ts +0 -2
- package/dist/lib/persona.test.d.ts.map +0 -1
- package/dist/lib/persona.test.js +0 -324
- package/dist/lib/persona.test.js.map +0 -1
- package/dist/lib/processor.test.d.ts +0 -2
- package/dist/lib/processor.test.d.ts.map +0 -1
- package/dist/lib/processor.test.js +0 -134
- package/dist/lib/processor.test.js.map +0 -1
- package/dist/lib/registry.test.d.ts +0 -2
- package/dist/lib/registry.test.d.ts.map +0 -1
- package/dist/lib/registry.test.js +0 -236
- package/dist/lib/registry.test.js.map +0 -1
- package/dist/lib/session-thread.test.d.ts +0 -2
- package/dist/lib/session-thread.test.d.ts.map +0 -1
- package/dist/lib/session-thread.test.js +0 -235
- package/dist/lib/session-thread.test.js.map +0 -1
- package/dist/lib/session.test.d.ts +0 -2
- package/dist/lib/session.test.d.ts.map +0 -1
- package/dist/lib/session.test.js +0 -336
- package/dist/lib/session.test.js.map +0 -1
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { hasFrontmatter, parseFrontmatter } from "../../lib/frontmatter.js";
|
|
2
|
+
/**
|
|
3
|
+
* Check if a message is a self-reply (from the same persona)
|
|
4
|
+
*
|
|
5
|
+
* Parses the from: field from message frontmatter and compares it
|
|
6
|
+
* against the target persona name. Returns true if the message
|
|
7
|
+
* should be skipped (i.e., it's from the same persona).
|
|
8
|
+
*
|
|
9
|
+
* Fails open: if the from field can't be parsed or is missing,
|
|
10
|
+
* the message is NOT skipped to avoid dropping legitimate messages.
|
|
11
|
+
*
|
|
12
|
+
* @param messageContent - The raw message content (may include frontmatter)
|
|
13
|
+
* @param personaName - The name of the target persona (without @ prefix)
|
|
14
|
+
* @returns true if the message should be skipped (self-reply)
|
|
15
|
+
*/
|
|
16
|
+
export function isSelfReply(messageContent, personaName) {
|
|
17
|
+
// Can't parse frontmatter -> fail open (don't skip)
|
|
18
|
+
if (!hasFrontmatter(messageContent)) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const { frontmatter } = parseFrontmatter(messageContent);
|
|
23
|
+
// Missing from field -> fail open (don't skip)
|
|
24
|
+
if (!frontmatter.from) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const from = frontmatter.from;
|
|
28
|
+
// The from field can be in various formats:
|
|
29
|
+
// - "agent:persona-name" (agent sender)
|
|
30
|
+
// - "@persona-name" (DM address format)
|
|
31
|
+
// - "human:username" (human sender)
|
|
32
|
+
// - Just "persona-name" (simple format)
|
|
33
|
+
// Normalize the from field - extract the persona name
|
|
34
|
+
let senderName = from;
|
|
35
|
+
// Strip agent: prefix
|
|
36
|
+
if (senderName.startsWith("agent:")) {
|
|
37
|
+
senderName = senderName.slice(6);
|
|
38
|
+
}
|
|
39
|
+
// Strip @ prefix
|
|
40
|
+
if (senderName.startsWith("@")) {
|
|
41
|
+
senderName = senderName.slice(1);
|
|
42
|
+
}
|
|
43
|
+
// Compare normalized sender to target persona
|
|
44
|
+
return senderName === personaName;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Parse error -> fail open (don't skip)
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Rate limiter for persona invocations
|
|
53
|
+
*
|
|
54
|
+
* Tracks invocations per persona with a sliding window.
|
|
55
|
+
* In-memory state - resets on daemon restart.
|
|
56
|
+
*/
|
|
57
|
+
export class RateLimiter {
|
|
58
|
+
maxInvocations;
|
|
59
|
+
windowMs;
|
|
60
|
+
/** Map of persona name -> list of invocation timestamps */
|
|
61
|
+
invocations = new Map();
|
|
62
|
+
/**
|
|
63
|
+
* Create a new rate limiter
|
|
64
|
+
*
|
|
65
|
+
* @param maxInvocations - Maximum invocations per window (default: 5)
|
|
66
|
+
* @param windowMs - Time window in milliseconds (default: 60000 = 1 minute)
|
|
67
|
+
*/
|
|
68
|
+
constructor(maxInvocations = 5, windowMs = 60_000) {
|
|
69
|
+
this.maxInvocations = maxInvocations;
|
|
70
|
+
this.windowMs = windowMs;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Check if an invocation should be allowed for a persona
|
|
74
|
+
*
|
|
75
|
+
* @param personaName - The persona to check
|
|
76
|
+
* @returns true if the invocation should be allowed, false if rate limited
|
|
77
|
+
*/
|
|
78
|
+
isAllowed(personaName) {
|
|
79
|
+
const now = Date.now();
|
|
80
|
+
const timestamps = this.invocations.get(personaName) ?? [];
|
|
81
|
+
// Filter to only timestamps within the window
|
|
82
|
+
const windowStart = now - this.windowMs;
|
|
83
|
+
const recentTimestamps = timestamps.filter((ts) => ts > windowStart);
|
|
84
|
+
// Check if under limit
|
|
85
|
+
return recentTimestamps.length < this.maxInvocations;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Record an invocation for a persona
|
|
89
|
+
*
|
|
90
|
+
* Should be called when an invocation is actually made (after isAllowed check passes).
|
|
91
|
+
*
|
|
92
|
+
* @param personaName - The persona being invoked
|
|
93
|
+
*/
|
|
94
|
+
recordInvocation(personaName) {
|
|
95
|
+
const now = Date.now();
|
|
96
|
+
const timestamps = this.invocations.get(personaName) ?? [];
|
|
97
|
+
// Clean up old timestamps and add new one
|
|
98
|
+
const windowStart = now - this.windowMs;
|
|
99
|
+
const recentTimestamps = timestamps.filter((ts) => ts > windowStart);
|
|
100
|
+
recentTimestamps.push(now);
|
|
101
|
+
this.invocations.set(personaName, recentTimestamps);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Check if allowed and record in one operation
|
|
105
|
+
*
|
|
106
|
+
* @param personaName - The persona to check
|
|
107
|
+
* @returns true if the invocation was allowed and recorded, false if rate limited
|
|
108
|
+
*/
|
|
109
|
+
tryInvoke(personaName) {
|
|
110
|
+
if (!this.isAllowed(personaName)) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
this.recordInvocation(personaName);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get current invocation count for a persona within the window
|
|
118
|
+
*
|
|
119
|
+
* @param personaName - The persona to check
|
|
120
|
+
* @returns Number of recent invocations
|
|
121
|
+
*/
|
|
122
|
+
getInvocationCount(personaName) {
|
|
123
|
+
const now = Date.now();
|
|
124
|
+
const timestamps = this.invocations.get(personaName) ?? [];
|
|
125
|
+
const windowStart = now - this.windowMs;
|
|
126
|
+
return timestamps.filter((ts) => ts > windowStart).length;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Reset all rate limiting state (for testing)
|
|
130
|
+
*/
|
|
131
|
+
reset() {
|
|
132
|
+
this.invocations.clear();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Circuit breaker for daemon-wide failure protection
|
|
137
|
+
*
|
|
138
|
+
* Tracks consecutive failures across all executions. When failures exceed
|
|
139
|
+
* a threshold within a time window, the breaker "trips" and blocks new
|
|
140
|
+
* executions until a cooldown period passes.
|
|
141
|
+
*
|
|
142
|
+
* This protects against cascading failures, external service outages,
|
|
143
|
+
* and unforeseen doom loops that bypass other safeguards.
|
|
144
|
+
*/
|
|
145
|
+
export class CircuitBreaker {
|
|
146
|
+
failureThreshold;
|
|
147
|
+
windowMs;
|
|
148
|
+
cooldownMs;
|
|
149
|
+
/** Timestamps of recent failures */
|
|
150
|
+
failures = [];
|
|
151
|
+
/** Whether the breaker is currently tripped */
|
|
152
|
+
tripped = false;
|
|
153
|
+
/** When the breaker was tripped (for cooldown calculation) */
|
|
154
|
+
trippedAt = null;
|
|
155
|
+
/**
|
|
156
|
+
* Create a new circuit breaker
|
|
157
|
+
*
|
|
158
|
+
* @param failureThreshold - Number of failures to trigger trip (default: 10)
|
|
159
|
+
* @param windowMs - Time window for counting failures (default: 60000 = 1 minute)
|
|
160
|
+
* @param cooldownMs - Time before auto-reset after trip (default: 300000 = 5 minutes)
|
|
161
|
+
*/
|
|
162
|
+
constructor(failureThreshold = 10, windowMs = 60_000, cooldownMs = 300_000) {
|
|
163
|
+
this.failureThreshold = failureThreshold;
|
|
164
|
+
this.windowMs = windowMs;
|
|
165
|
+
this.cooldownMs = cooldownMs;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Record a failure
|
|
169
|
+
*
|
|
170
|
+
* Adds the failure to the tracking window and checks if the breaker should trip.
|
|
171
|
+
*/
|
|
172
|
+
recordFailure() {
|
|
173
|
+
const now = Date.now();
|
|
174
|
+
this.failures.push(now);
|
|
175
|
+
// Clean old failures outside the window
|
|
176
|
+
const windowStart = now - this.windowMs;
|
|
177
|
+
this.failures = this.failures.filter((ts) => ts > windowStart);
|
|
178
|
+
// Check if we should trip
|
|
179
|
+
if (!this.tripped && this.failures.length >= this.failureThreshold) {
|
|
180
|
+
this.tripped = true;
|
|
181
|
+
this.trippedAt = now;
|
|
182
|
+
console.error(`[circuit-breaker] TRIPPED: ${this.failures.length} failures in ${this.windowMs / 1000}s. ` +
|
|
183
|
+
`Blocking executions for ${this.cooldownMs / 1000}s.`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Record a success
|
|
188
|
+
*
|
|
189
|
+
* Clears the failure count on success (indicates system is healthy).
|
|
190
|
+
*/
|
|
191
|
+
recordSuccess() {
|
|
192
|
+
this.failures = [];
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Check if the breaker is currently tripped
|
|
196
|
+
*
|
|
197
|
+
* Also handles auto-reset after cooldown period.
|
|
198
|
+
*
|
|
199
|
+
* @returns true if executions should be blocked
|
|
200
|
+
*/
|
|
201
|
+
isTripped() {
|
|
202
|
+
if (!this.tripped) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
// Check if cooldown has passed
|
|
206
|
+
const now = Date.now();
|
|
207
|
+
if (this.trippedAt && now - this.trippedAt >= this.cooldownMs) {
|
|
208
|
+
console.log(`[circuit-breaker] Auto-reset after ${this.cooldownMs / 1000}s cooldown. Allowing executions.`);
|
|
209
|
+
this.reset();
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Get time remaining until auto-reset (in seconds)
|
|
216
|
+
*
|
|
217
|
+
* @returns Seconds until reset, or 0 if not tripped
|
|
218
|
+
*/
|
|
219
|
+
getTimeUntilReset() {
|
|
220
|
+
if (!this.tripped || !this.trippedAt) {
|
|
221
|
+
return 0;
|
|
222
|
+
}
|
|
223
|
+
const elapsed = Date.now() - this.trippedAt;
|
|
224
|
+
const remaining = this.cooldownMs - elapsed;
|
|
225
|
+
return Math.max(0, Math.ceil(remaining / 1000));
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Manually reset the breaker
|
|
229
|
+
*/
|
|
230
|
+
reset() {
|
|
231
|
+
this.tripped = false;
|
|
232
|
+
this.trippedAt = null;
|
|
233
|
+
this.failures = [];
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Get current state for status reporting
|
|
237
|
+
*/
|
|
238
|
+
getState() {
|
|
239
|
+
// Clean old failures for accurate count
|
|
240
|
+
const now = Date.now();
|
|
241
|
+
const windowStart = now - this.windowMs;
|
|
242
|
+
this.failures = this.failures.filter((ts) => ts > windowStart);
|
|
243
|
+
return {
|
|
244
|
+
tripped: this.tripped,
|
|
245
|
+
failureCount: this.failures.length,
|
|
246
|
+
timeUntilReset: this.getTimeUntilReset(),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=safeguards.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safeguards.js","sourceRoot":"","sources":["../../../src/daemon/lib/safeguards.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../../../src/daemon/lib/watcher.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../../../src/daemon/lib/watcher.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAa3C;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,gBAAgB,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACnC,kBAAkB,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACrC,kBAAkB,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACrC,eAAe,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAClC,iBAAiB,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,iBAAiB,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,aAAa,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3E,iBAAiB,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;CAChF;AAED;;GAEG;AACH,qBAAa,OAAQ,SAAQ,YAAY;IAOrC,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,WAAW,CAAC;IARtB,OAAO,CAAC,eAAe,CAA0B;IACjD,OAAO,CAAC,cAAc,CAA0B;IAChD,OAAO,CAAC,cAAc,CAA0B;IAChD,OAAO,CAAC,OAAO,CAAS;gBAGd,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,MAAM,YAAA;IAK9B;;OAEG;IACH,KAAK,IAAI,IAAI;IA4Gb;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB3B;;OAEG;IACH,SAAS,IAAI,OAAO;CAGrB"}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { watch } from "chokidar";
|
|
2
2
|
import { EventEmitter } from "node:events";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
3
4
|
import { dirname, basename } from "node:path";
|
|
5
|
+
/**
|
|
6
|
+
* Check if a string looks like an ISO timestamp (message ID format)
|
|
7
|
+
* Message IDs are ISO timestamps like "2026-01-24T23:29:20.778Z"
|
|
8
|
+
* UUIDs are like "621f4c3e-69f8-4c16-994b-3cbda5e27f97"
|
|
9
|
+
*/
|
|
10
|
+
function isISOTimestamp(str) {
|
|
11
|
+
return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(str);
|
|
12
|
+
}
|
|
4
13
|
/**
|
|
5
14
|
* File watcher for workflows, personas, and channels
|
|
6
15
|
*/
|
|
@@ -60,17 +69,25 @@ export class Watcher extends EventEmitter {
|
|
|
60
69
|
this.channelWatcher = watch(this.channelsDir, {
|
|
61
70
|
ignoreInitial: true,
|
|
62
71
|
persistent: true,
|
|
63
|
-
depth: 3, // channels/{@name|#name}/
|
|
72
|
+
depth: 3, // channels/{@name|#name}/{thread-id}/{message-id}.md
|
|
73
|
+
// Wait for files to stabilize before emitting events
|
|
74
|
+
// This helps with cloud-synced files (iCloud, Syncthing) that appear
|
|
75
|
+
// in the filesystem before they're fully written/readable
|
|
76
|
+
awaitWriteFinish: {
|
|
77
|
+
stabilityThreshold: 500, // Wait 500ms after last change
|
|
78
|
+
pollInterval: 100,
|
|
79
|
+
},
|
|
64
80
|
});
|
|
65
|
-
this.channelWatcher.on("add", (path) => {
|
|
66
|
-
// Only process
|
|
67
|
-
if (!path.endsWith("
|
|
81
|
+
this.channelWatcher.on("add", async (path) => {
|
|
82
|
+
// Only process .md message files
|
|
83
|
+
if (!path.endsWith(".md")) {
|
|
68
84
|
return;
|
|
69
85
|
}
|
|
70
|
-
// Path: {channelsDir}/{@persona|#channel}/{
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
const
|
|
86
|
+
// Path: {channelsDir}/{@persona|#channel}/{thread-id}/{message-id}.md
|
|
87
|
+
const messageId = basename(path, ".md");
|
|
88
|
+
const threadDir = dirname(path);
|
|
89
|
+
const threadId = basename(threadDir);
|
|
90
|
+
const channelDir = dirname(threadDir);
|
|
74
91
|
const channel = basename(channelDir);
|
|
75
92
|
// Emit appropriate event based on channel type
|
|
76
93
|
if (channel.startsWith("@")) {
|
|
@@ -79,6 +96,29 @@ export class Watcher extends EventEmitter {
|
|
|
79
96
|
}
|
|
80
97
|
else if (channel.startsWith("#")) {
|
|
81
98
|
// Public channel -> trigger workflow
|
|
99
|
+
// Skip thread replies by checking frontmatter thread_id
|
|
100
|
+
// - New messages have UUID thread_id (random identifier)
|
|
101
|
+
// - Replies have timestamp thread_id (pointing to another message)
|
|
102
|
+
try {
|
|
103
|
+
const content = await readFile(path, "utf-8");
|
|
104
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
105
|
+
if (frontmatterMatch) {
|
|
106
|
+
const threadIdMatch = frontmatterMatch[1].match(/thread_id:\s*["']?([^\s"'\n]+)/);
|
|
107
|
+
if (threadIdMatch) {
|
|
108
|
+
const frontmatterThreadId = threadIdMatch[1];
|
|
109
|
+
// If thread_id is a timestamp (not UUID), it's a reply - skip it
|
|
110
|
+
if (isISOTimestamp(frontmatterThreadId)) {
|
|
111
|
+
console.log(`[watcher] Skipping thread reply: ${messageId} (reply to: ${frontmatterThreadId})`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// If we can't read the file, skip it (fail closed for safety)
|
|
119
|
+
console.warn(`[watcher] Could not read message file: ${path}`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
82
122
|
this.emit("channel:message", { channel, messageId, messagePath: path });
|
|
83
123
|
}
|
|
84
124
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watcher.js","sourceRoot":"","sources":["../../../src/daemon/lib/watcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAkB,MAAM,UAAU,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"watcher.js","sourceRoot":"","sources":["../../../src/daemon/lib/watcher.ts"],"names":[],"mappings":"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"}
|