dot-agents 0.5.0 → 0.7.4
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 +239 -122
- package/dist/cli/commands/channel.d.ts +19 -0
- package/dist/cli/commands/channel.d.ts.map +1 -1
- package/dist/cli/commands/channel.js +220 -13
- package/dist/cli/commands/channel.js.map +1 -1
- package/dist/cli/commands/check.d.ts.map +1 -1
- package/dist/cli/commands/check.js +61 -1
- package/dist/cli/commands/check.js.map +1 -1
- package/dist/cli/commands/index.d.ts +2 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +2 -0
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/personas.d.ts +3 -0
- package/dist/cli/commands/personas.d.ts.map +1 -0
- package/dist/cli/commands/personas.js +402 -0
- package/dist/cli/commands/personas.js.map +1 -0
- package/dist/cli/commands/projects.d.ts +3 -0
- package/dist/cli/commands/projects.d.ts.map +1 -0
- package/dist/cli/commands/projects.js +138 -0
- package/dist/cli/commands/projects.js.map +1 -0
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/run.js +4 -5
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/index.js +3 -11
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/lib/runner.d.ts +2 -0
- package/dist/cli/lib/runner.d.ts.map +1 -1
- package/dist/cli/lib/runner.js +67 -8
- package/dist/cli/lib/runner.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 +56 -0
- package/dist/daemon/api/server.js.map +1 -1
- package/dist/daemon/daemon.d.ts +28 -3
- package/dist/daemon/daemon.d.ts.map +1 -1
- package/dist/daemon/daemon.js +142 -26
- package/dist/daemon/daemon.js.map +1 -1
- package/dist/daemon/lib/executor.d.ts +0 -4
- package/dist/daemon/lib/executor.d.ts.map +1 -1
- package/dist/daemon/lib/executor.js +48 -135
- package/dist/daemon/lib/executor.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 +68 -0
- package/dist/daemon/lib/safeguards.d.ts.map +1 -0
- package/dist/daemon/lib/safeguards.js +135 -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/channel.d.ts +53 -1
- package/dist/lib/channel.d.ts.map +1 -1
- package/dist/lib/channel.js +221 -30
- package/dist/lib/channel.js.map +1 -1
- package/dist/lib/daemon-status.d.ts +26 -0
- package/dist/lib/daemon-status.d.ts.map +1 -0
- package/dist/lib/daemon-status.js +64 -0
- package/dist/lib/daemon-status.js.map +1 -0
- package/dist/lib/environment.d.ts +88 -0
- package/dist/lib/environment.d.ts.map +1 -0
- package/dist/lib/environment.js +238 -0
- package/dist/lib/environment.js.map +1 -0
- package/dist/lib/frontmatter.d.ts +8 -0
- package/dist/lib/frontmatter.d.ts.map +1 -1
- package/dist/lib/frontmatter.js +16 -3
- package/dist/lib/frontmatter.js.map +1 -1
- package/dist/lib/index.d.ts +7 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +7 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/invoke.d.ts +31 -0
- package/dist/lib/invoke.d.ts.map +1 -0
- package/dist/lib/invoke.js +170 -0
- package/dist/lib/invoke.js.map +1 -0
- package/dist/lib/persona.d.ts +22 -1
- package/dist/lib/persona.d.ts.map +1 -1
- package/dist/lib/persona.js +176 -31
- package/dist/lib/persona.js.map +1 -1
- package/dist/lib/processor.d.ts +58 -0
- package/dist/lib/processor.d.ts.map +1 -0
- package/dist/lib/processor.js +149 -0
- package/dist/lib/processor.js.map +1 -0
- package/dist/lib/registry.d.ts +109 -0
- package/dist/lib/registry.d.ts.map +1 -0
- package/dist/lib/registry.js +192 -0
- package/dist/lib/registry.js.map +1 -0
- package/dist/lib/session-thread.d.ts +75 -0
- package/dist/lib/session-thread.d.ts.map +1 -0
- package/dist/lib/session-thread.js +132 -0
- package/dist/lib/session-thread.js.map +1 -0
- package/dist/lib/session.d.ts +150 -0
- package/dist/lib/session.d.ts.map +1 -0
- package/dist/lib/session.js +183 -0
- package/dist/lib/session.js.map +1 -0
- package/dist/lib/types/channel.d.ts +4 -0
- package/dist/lib/types/channel.d.ts.map +1 -1
- package/dist/lib/types/persona.d.ts +49 -6
- package/dist/lib/types/persona.d.ts.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/internal/personas/_base/PERSONA.md +222 -39
- package/internal/skills/channels/list/SKILL.md +76 -0
- package/package.json +15 -5
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if a message is a self-reply (from the same persona)
|
|
3
|
+
*
|
|
4
|
+
* Parses the from: field from message frontmatter and compares it
|
|
5
|
+
* against the target persona name. Returns true if the message
|
|
6
|
+
* should be skipped (i.e., it's from the same persona).
|
|
7
|
+
*
|
|
8
|
+
* Fails open: if the from field can't be parsed or is missing,
|
|
9
|
+
* the message is NOT skipped to avoid dropping legitimate messages.
|
|
10
|
+
*
|
|
11
|
+
* @param messageContent - The raw message content (may include frontmatter)
|
|
12
|
+
* @param personaName - The name of the target persona (without @ prefix)
|
|
13
|
+
* @returns true if the message should be skipped (self-reply)
|
|
14
|
+
*/
|
|
15
|
+
export declare function isSelfReply(messageContent: string, personaName: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Rate limiter for persona invocations
|
|
18
|
+
*
|
|
19
|
+
* Tracks invocations per persona with a sliding window.
|
|
20
|
+
* In-memory state - resets on daemon restart.
|
|
21
|
+
*/
|
|
22
|
+
export declare class RateLimiter {
|
|
23
|
+
private maxInvocations;
|
|
24
|
+
private windowMs;
|
|
25
|
+
/** Map of persona name -> list of invocation timestamps */
|
|
26
|
+
private invocations;
|
|
27
|
+
/**
|
|
28
|
+
* Create a new rate limiter
|
|
29
|
+
*
|
|
30
|
+
* @param maxInvocations - Maximum invocations per window (default: 5)
|
|
31
|
+
* @param windowMs - Time window in milliseconds (default: 60000 = 1 minute)
|
|
32
|
+
*/
|
|
33
|
+
constructor(maxInvocations?: number, windowMs?: number);
|
|
34
|
+
/**
|
|
35
|
+
* Check if an invocation should be allowed for a persona
|
|
36
|
+
*
|
|
37
|
+
* @param personaName - The persona to check
|
|
38
|
+
* @returns true if the invocation should be allowed, false if rate limited
|
|
39
|
+
*/
|
|
40
|
+
isAllowed(personaName: string): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Record an invocation for a persona
|
|
43
|
+
*
|
|
44
|
+
* Should be called when an invocation is actually made (after isAllowed check passes).
|
|
45
|
+
*
|
|
46
|
+
* @param personaName - The persona being invoked
|
|
47
|
+
*/
|
|
48
|
+
recordInvocation(personaName: string): void;
|
|
49
|
+
/**
|
|
50
|
+
* Check if allowed and record in one operation
|
|
51
|
+
*
|
|
52
|
+
* @param personaName - The persona to check
|
|
53
|
+
* @returns true if the invocation was allowed and recorded, false if rate limited
|
|
54
|
+
*/
|
|
55
|
+
tryInvoke(personaName: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Get current invocation count for a persona within the window
|
|
58
|
+
*
|
|
59
|
+
* @param personaName - The persona to check
|
|
60
|
+
* @returns Number of recent invocations
|
|
61
|
+
*/
|
|
62
|
+
getInvocationCount(personaName: string): number;
|
|
63
|
+
/**
|
|
64
|
+
* Reset all rate limiting state (for testing)
|
|
65
|
+
*/
|
|
66
|
+
reset(): void;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=safeguards.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safeguards.d.ts","sourceRoot":"","sources":["../../../src/daemon/lib/safeguards.ts"],"names":[],"mappings":"AAUA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CACzB,cAAc,EAAE,MAAM,EACtB,WAAW,EAAE,MAAM,GAClB,OAAO,CAyCT;AAED;;;;;GAKG;AACH,qBAAa,WAAW;IAWpB,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,QAAQ;IAXlB,2DAA2D;IAC3D,OAAO,CAAC,WAAW,CAAoC;IAEvD;;;;;OAKG;gBAEO,cAAc,GAAE,MAAU,EAC1B,QAAQ,GAAE,MAAe;IAGnC;;;;;OAKG;IACH,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAYvC;;;;;;OAMG;IACH,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAY3C;;;;;OAKG;IACH,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAQvC;;;;;OAKG;IACH,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAO/C;;OAEG;IACH,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -0,0 +1,135 @@
|
|
|
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
|
+
//# 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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"}
|