dot-agents 0.5.0 → 0.6.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.
- 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 +152 -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/daemon.d.ts +10 -3
- package/dist/daemon/daemon.d.ts.map +1 -1
- package/dist/daemon/daemon.js +19 -21
- 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/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/channel.test.d.ts +2 -0
- package/dist/lib/channel.test.d.ts.map +1 -0
- package/dist/lib/channel.test.js +33 -0
- package/dist/lib/channel.test.js.map +1 -0
- 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 +87 -0
- package/dist/lib/environment.d.ts.map +1 -0
- package/dist/lib/environment.js +224 -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/frontmatter.test.d.ts +2 -0
- package/dist/lib/frontmatter.test.d.ts.map +1 -0
- package/dist/lib/frontmatter.test.js +60 -0
- package/dist/lib/frontmatter.test.js.map +1 -0
- 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/integration.test.d.ts +2 -0
- package/dist/lib/integration.test.d.ts.map +1 -0
- package/dist/lib/integration.test.js +445 -0
- package/dist/lib/integration.test.js.map +1 -0
- package/dist/lib/invoke.d.ts +23 -0
- package/dist/lib/invoke.d.ts.map +1 -0
- package/dist/lib/invoke.js +158 -0
- package/dist/lib/invoke.js.map +1 -0
- package/dist/lib/invoke.test.d.ts +2 -0
- package/dist/lib/invoke.test.d.ts.map +1 -0
- package/dist/lib/invoke.test.js +82 -0
- package/dist/lib/invoke.test.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/persona.test.d.ts +2 -0
- package/dist/lib/persona.test.d.ts.map +1 -0
- package/dist/lib/persona.test.js +324 -0
- package/dist/lib/persona.test.js.map +1 -0
- package/dist/lib/processor.d.ts +50 -0
- package/dist/lib/processor.d.ts.map +1 -0
- package/dist/lib/processor.js +135 -0
- package/dist/lib/processor.js.map +1 -0
- package/dist/lib/processor.test.d.ts +2 -0
- package/dist/lib/processor.test.d.ts.map +1 -0
- package/dist/lib/processor.test.js +134 -0
- package/dist/lib/processor.test.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/registry.test.d.ts +2 -0
- package/dist/lib/registry.test.d.ts.map +1 -0
- package/dist/lib/registry.test.js +236 -0
- package/dist/lib/registry.test.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-thread.test.d.ts +2 -0
- package/dist/lib/session-thread.test.d.ts.map +1 -0
- package/dist/lib/session-thread.test.js +235 -0
- package/dist/lib/session-thread.test.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/session.test.d.ts +2 -0
- package/dist/lib/session.test.d.ts.map +1 -0
- package/dist/lib/session.test.js +336 -0
- package/dist/lib/session.test.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/internal/personas/_base/PERSONA.md +222 -39
- package/internal/skills/channels/list/SKILL.md +76 -0
- package/package.json +6 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { execa } from "execa";
|
|
2
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
2
|
import { join } from "node:path";
|
|
4
|
-
import {
|
|
3
|
+
import { hostname } from "node:os";
|
|
4
|
+
import { resolvePersona, createExecutionContext, processTemplate, expandVariables, getInputDefaults, createSession, finalizeSession, invokePersona as invokePersonaLib, } from "../../lib/index.js";
|
|
5
5
|
/**
|
|
6
6
|
* Parse duration string to milliseconds
|
|
7
7
|
*/
|
|
@@ -53,12 +53,35 @@ export class Executor {
|
|
|
53
53
|
const startedAt = new Date();
|
|
54
54
|
// Resolve persona
|
|
55
55
|
const persona = await resolvePersona(join(this.config.personasDir, workflow.persona), this.config.personasDir);
|
|
56
|
+
// Determine working directory early (needed for session creation)
|
|
57
|
+
let workingDir = workflow.working_dir ?? process.cwd();
|
|
58
|
+
// Create session
|
|
59
|
+
const session = await createSession({
|
|
60
|
+
sessionsDir: this.config.sessionsDir,
|
|
61
|
+
runtime: {
|
|
62
|
+
hostname: hostname(),
|
|
63
|
+
executionMode: interactive ? "interactive" : "headless",
|
|
64
|
+
triggerType: "cron",
|
|
65
|
+
workingDir,
|
|
66
|
+
},
|
|
67
|
+
goal: `Run workflow: ${workflow.name}`,
|
|
68
|
+
persona: {
|
|
69
|
+
name: persona.name,
|
|
70
|
+
inheritanceChain: persona.inheritanceChain,
|
|
71
|
+
},
|
|
72
|
+
workflow: {
|
|
73
|
+
name: workflow.name,
|
|
74
|
+
path: workflow.path,
|
|
75
|
+
inputs: inputOverrides,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
56
78
|
const context = createExecutionContext({
|
|
57
79
|
WORKFLOW_NAME: workflow.name,
|
|
58
80
|
WORKFLOW_DIR: workflow.path,
|
|
59
81
|
PERSONA_NAME: persona.name,
|
|
60
82
|
PERSONA_DIR: persona.path,
|
|
61
83
|
AGENTS_DIR: this.config.agentsDir,
|
|
84
|
+
SESSION_DIR: session.path,
|
|
62
85
|
});
|
|
63
86
|
// Merge inputs
|
|
64
87
|
const inputs = {
|
|
@@ -77,8 +100,7 @@ export class Executor {
|
|
|
77
100
|
env[key] = expandVariables(value, context, env);
|
|
78
101
|
}
|
|
79
102
|
}
|
|
80
|
-
//
|
|
81
|
-
let workingDir = workflow.working_dir ?? process.cwd();
|
|
103
|
+
// Expand working directory variables
|
|
82
104
|
workingDir = expandVariables(workingDir, { ...context, ...inputs }, env);
|
|
83
105
|
// Build prompt
|
|
84
106
|
const prompt = buildPrompt(workflow, persona, context, inputs);
|
|
@@ -92,7 +114,7 @@ export class Executor {
|
|
|
92
114
|
: persona.commands.headless;
|
|
93
115
|
if (!cmds) {
|
|
94
116
|
const endedAt = new Date();
|
|
95
|
-
|
|
117
|
+
const result = {
|
|
96
118
|
success: false,
|
|
97
119
|
exitCode: 1,
|
|
98
120
|
stdout: "",
|
|
@@ -103,6 +125,15 @@ export class Executor {
|
|
|
103
125
|
endedAt,
|
|
104
126
|
error: `Unsupported execution mode: ${interactive ? "interactive" : "headless"}`,
|
|
105
127
|
};
|
|
128
|
+
// Finalize session for error
|
|
129
|
+
await finalizeSession(session, {
|
|
130
|
+
success: result.success,
|
|
131
|
+
exitCode: result.exitCode,
|
|
132
|
+
duration: result.duration,
|
|
133
|
+
error: result.error,
|
|
134
|
+
stderr: result.stderr,
|
|
135
|
+
});
|
|
136
|
+
return result;
|
|
106
137
|
}
|
|
107
138
|
// Try each command
|
|
108
139
|
let lastError = null;
|
|
@@ -151,8 +182,15 @@ export class Executor {
|
|
|
151
182
|
error: lastError?.message,
|
|
152
183
|
};
|
|
153
184
|
}
|
|
154
|
-
//
|
|
155
|
-
await
|
|
185
|
+
// Finalize session
|
|
186
|
+
await finalizeSession(session, {
|
|
187
|
+
success: result.success,
|
|
188
|
+
exitCode: result.exitCode,
|
|
189
|
+
duration: result.duration,
|
|
190
|
+
error: result.error,
|
|
191
|
+
stdout: result.stdout,
|
|
192
|
+
stderr: result.stderr,
|
|
193
|
+
});
|
|
156
194
|
return result;
|
|
157
195
|
}
|
|
158
196
|
/**
|
|
@@ -160,135 +198,10 @@ export class Executor {
|
|
|
160
198
|
* Used for DM-triggered invocations
|
|
161
199
|
*/
|
|
162
200
|
async invokePersona(personaName, message, options = {}) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const context = createExecutionContext({
|
|
167
|
-
PERSONA_NAME: persona.name,
|
|
168
|
-
PERSONA_DIR: persona.path,
|
|
169
|
-
AGENTS_DIR: this.config.agentsDir,
|
|
170
|
-
INVOCATION_SOURCE: options.source ?? "dm",
|
|
171
|
-
...options.context,
|
|
201
|
+
return invokePersonaLib(this.config, personaName, message, {
|
|
202
|
+
source: options.source ?? "dm",
|
|
203
|
+
context: options.context,
|
|
172
204
|
});
|
|
173
|
-
// Merge environment
|
|
174
|
-
const env = {
|
|
175
|
-
...process.env,
|
|
176
|
-
...persona.env,
|
|
177
|
-
DOT_AGENTS_PERSONA: persona.name,
|
|
178
|
-
DOT_AGENTS_INVOCATION: "dm",
|
|
179
|
-
};
|
|
180
|
-
for (const [key, value] of Object.entries(env)) {
|
|
181
|
-
if (value) {
|
|
182
|
-
env[key] = expandVariables(value, context, env);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
// Build prompt: persona prompt + message
|
|
186
|
-
const parts = [];
|
|
187
|
-
if (persona.prompt) {
|
|
188
|
-
const expandedPrompt = processTemplate(persona.prompt, context, persona.env);
|
|
189
|
-
parts.push(expandedPrompt);
|
|
190
|
-
parts.push("\n---\n");
|
|
191
|
-
}
|
|
192
|
-
parts.push(`You received a direct message:\n\n${message}`);
|
|
193
|
-
const prompt = parts.join("\n");
|
|
194
|
-
// Timeout (default 10 minutes for DM invocations)
|
|
195
|
-
const timeoutMs = 10 * 60 * 1000;
|
|
196
|
-
// Use headless commands for DM-triggered invocations
|
|
197
|
-
const cmds = persona.commands.headless;
|
|
198
|
-
if (!cmds) {
|
|
199
|
-
const endedAt = new Date();
|
|
200
|
-
return {
|
|
201
|
-
success: false,
|
|
202
|
-
exitCode: 1,
|
|
203
|
-
stdout: "",
|
|
204
|
-
stderr: `Persona '${persona.name}' does not support headless mode`,
|
|
205
|
-
duration: endedAt.getTime() - startedAt.getTime(),
|
|
206
|
-
runId: context.RUN_ID,
|
|
207
|
-
startedAt,
|
|
208
|
-
endedAt,
|
|
209
|
-
error: "Persona does not support headless mode",
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
// Try each command
|
|
213
|
-
let lastError = null;
|
|
214
|
-
let result = null;
|
|
215
|
-
for (const cmd of cmds) {
|
|
216
|
-
try {
|
|
217
|
-
const expandedCmd = expandVariables(cmd, context, env);
|
|
218
|
-
const [command, ...args] = expandedCmd.split(/\s+/);
|
|
219
|
-
const execResult = await execa(command, args, {
|
|
220
|
-
input: prompt,
|
|
221
|
-
cwd: this.config.agentsDir,
|
|
222
|
-
env,
|
|
223
|
-
timeout: timeoutMs,
|
|
224
|
-
reject: false,
|
|
225
|
-
});
|
|
226
|
-
const endedAt = new Date();
|
|
227
|
-
result = {
|
|
228
|
-
success: execResult.exitCode === 0,
|
|
229
|
-
exitCode: execResult.exitCode ?? 1,
|
|
230
|
-
stdout: execResult.stdout,
|
|
231
|
-
stderr: execResult.stderr,
|
|
232
|
-
duration: endedAt.getTime() - startedAt.getTime(),
|
|
233
|
-
runId: context.RUN_ID,
|
|
234
|
-
startedAt,
|
|
235
|
-
endedAt,
|
|
236
|
-
};
|
|
237
|
-
if (result.success)
|
|
238
|
-
break;
|
|
239
|
-
lastError = new Error(`Command failed with exit code ${result.exitCode}: ${execResult.stderr}`);
|
|
240
|
-
}
|
|
241
|
-
catch (error) {
|
|
242
|
-
lastError = error;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
if (!result) {
|
|
246
|
-
const endedAt = new Date();
|
|
247
|
-
result = {
|
|
248
|
-
success: false,
|
|
249
|
-
exitCode: 1,
|
|
250
|
-
stdout: "",
|
|
251
|
-
stderr: lastError?.message ?? "All commands failed",
|
|
252
|
-
duration: endedAt.getTime() - startedAt.getTime(),
|
|
253
|
-
runId: context.RUN_ID,
|
|
254
|
-
startedAt,
|
|
255
|
-
endedAt,
|
|
256
|
-
error: lastError?.message,
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
// Write session log
|
|
260
|
-
await this.writeSessionLog(`dm:${options.source ?? personaName}`, persona.name, result);
|
|
261
|
-
return result;
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Write execution log to sessions directory
|
|
265
|
-
*/
|
|
266
|
-
async writeSessionLog(workflowName, personaName, result) {
|
|
267
|
-
await mkdir(this.config.sessionsDir, { recursive: true });
|
|
268
|
-
const timestamp = result.startedAt
|
|
269
|
-
.toISOString()
|
|
270
|
-
.replace(/[-:]/g, "")
|
|
271
|
-
.replace("T", "-")
|
|
272
|
-
.split(".")[0];
|
|
273
|
-
const filename = `${timestamp}.log`;
|
|
274
|
-
const logPath = join(this.config.sessionsDir, filename);
|
|
275
|
-
const logContent = `Run ID: ${result.runId}
|
|
276
|
-
Workflow: ${workflowName}
|
|
277
|
-
Persona: ${personaName}
|
|
278
|
-
Started: ${result.startedAt.toISOString()}
|
|
279
|
-
Ended: ${result.endedAt.toISOString()}
|
|
280
|
-
Duration: ${result.duration}ms
|
|
281
|
-
Exit Code: ${result.exitCode}
|
|
282
|
-
Success: ${result.success}
|
|
283
|
-
${result.error ? `Error: ${result.error}\n` : ""}
|
|
284
|
-
--- STDOUT ---
|
|
285
|
-
${result.stdout}
|
|
286
|
-
|
|
287
|
-
--- STDERR ---
|
|
288
|
-
${result.stderr}
|
|
289
|
-
`;
|
|
290
|
-
await writeFile(logPath, logContent, "utf-8");
|
|
291
|
-
return logPath;
|
|
292
205
|
}
|
|
293
206
|
}
|
|
294
207
|
//# sourceMappingURL=executor.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../src/daemon/lib/executor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAC9B,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../src/daemon/lib/executor.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/channel.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export declare function publishMessage(channelsDir: string, channelName: string,
|
|
|
21
21
|
/**
|
|
22
22
|
* Read messages from a channel
|
|
23
23
|
*/
|
|
24
|
-
export declare function readChannel(channelsDir: string, channelName: string, limit?: number, since?: Date): Promise<ChannelMessage[]>;
|
|
24
|
+
export declare function readChannel(channelsDir: string, channelName: string, limit?: number, since?: Date, threadId?: string): Promise<ChannelMessage[]>;
|
|
25
25
|
/**
|
|
26
26
|
* Reply to a message in a channel
|
|
27
27
|
*/
|
|
@@ -30,4 +30,56 @@ export declare function replyToMessage(channelsDir: string, channelName: string,
|
|
|
30
30
|
* Load channel metadata
|
|
31
31
|
*/
|
|
32
32
|
export declare function loadChannelMetadata(channelsDir: string, channelName: string): Promise<ChannelMetadata | null>;
|
|
33
|
+
/**
|
|
34
|
+
* Get the last processed timestamp for a channel
|
|
35
|
+
*/
|
|
36
|
+
export declare function getLastProcessedTime(channelsDir: string, channelName: string): Promise<Date | null>;
|
|
37
|
+
/**
|
|
38
|
+
* Mark a channel as processed at the current time
|
|
39
|
+
*/
|
|
40
|
+
export declare function markChannelProcessed(channelsDir: string, channelName: string): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* A pending message that needs processing
|
|
43
|
+
*/
|
|
44
|
+
export interface PendingMessage {
|
|
45
|
+
/** Message ID (ISO 8601 timestamp) */
|
|
46
|
+
id: string;
|
|
47
|
+
/** Channel name (e.g., @persona) */
|
|
48
|
+
channel: string;
|
|
49
|
+
/** Path to the message file */
|
|
50
|
+
path: string;
|
|
51
|
+
/** Message content (body, without frontmatter) */
|
|
52
|
+
content: string;
|
|
53
|
+
/** Message metadata */
|
|
54
|
+
meta: ChannelMessageMeta;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get pending (unprocessed) messages for a DM channel
|
|
58
|
+
* Returns messages newer than the last processed timestamp
|
|
59
|
+
*/
|
|
60
|
+
export declare function getPendingMessages(channelsDir: string, channelName: string): Promise<PendingMessage[]>;
|
|
61
|
+
/**
|
|
62
|
+
* List all DM channels (@persona) in the channels directory
|
|
63
|
+
*/
|
|
64
|
+
export declare function listDMChannels(channelsDir: string): Promise<string[]>;
|
|
65
|
+
/**
|
|
66
|
+
* Get the workspace directory path for a thread.
|
|
67
|
+
* The workspace is a directory within the thread for working files.
|
|
68
|
+
*
|
|
69
|
+
* @param channelsDir - Base channels directory
|
|
70
|
+
* @param channelName - Channel name (e.g., "#sessions")
|
|
71
|
+
* @param threadId - Thread ID (ISO timestamp)
|
|
72
|
+
* @param create - If true, creates the workspace directory if it doesn't exist
|
|
73
|
+
* @returns Path to the workspace directory, or null if thread doesn't exist
|
|
74
|
+
*/
|
|
75
|
+
export declare function getThreadWorkspace(channelsDir: string, channelName: string, threadId: string, create?: boolean): Promise<string | null>;
|
|
76
|
+
/**
|
|
77
|
+
* Get the thread directory path.
|
|
78
|
+
*
|
|
79
|
+
* @param channelsDir - Base channels directory
|
|
80
|
+
* @param channelName - Channel name (e.g., "#sessions")
|
|
81
|
+
* @param threadId - Thread ID (ISO timestamp)
|
|
82
|
+
* @returns Path to the thread directory, or null if it doesn't exist
|
|
83
|
+
*/
|
|
84
|
+
export declare function getThreadPath(channelsDir: string, channelName: string, threadId: string): Promise<string | null>;
|
|
33
85
|
//# sourceMappingURL=channel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../src/lib/channel.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../src/lib/channel.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,kBAAkB,EAEnB,MAAM,oBAAoB,CAAC;AAyD5B;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,eAAe,CAAA;CAAE,EAAE,CAAC,CAiCxD;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,SAAS,GAAE,MAAiB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,kBAAkB,GACxB,OAAO,CAAC,MAAM,CAAC,CAsBjB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,KAAK,CAAC,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,IAAI,EACZ,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,EAAE,CAAC,CA4E3B;AAmDD;;GAEG;AACH,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,kBAAkB,GACxB,OAAO,CAAC,MAAM,CAAC,CAqBjB;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CASjC;AAYD;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CActB;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAcf;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,sCAAsC;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,uBAAuB;IACvB,IAAI,EAAE,kBAAkB,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,cAAc,EAAE,CAAC,CA4D3B;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,EAAE,CAAC,CAgBnB;AAED;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,OAAe,GACtB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAcxB;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQxB"}
|
package/dist/lib/channel.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { readdir, mkdir, readFile, writeFile, stat } from "node:fs/promises";
|
|
2
|
+
import { hostname } from "node:os";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
4
5
|
import { parseFrontmatter } from "./frontmatter.js";
|
|
5
6
|
const METADATA_FILE = "_metadata.yaml";
|
|
6
|
-
const
|
|
7
|
+
const MESSAGE_FILE_LEGACY = "message.md"; // Backwards compatibility
|
|
8
|
+
const LAST_PROCESSED_FILE = "_last_processed.yaml";
|
|
7
9
|
/**
|
|
8
10
|
* Check if a path exists and is a directory
|
|
9
11
|
*/
|
|
@@ -34,6 +36,23 @@ async function isFile(path) {
|
|
|
34
36
|
function generateMessageId() {
|
|
35
37
|
return new Date().toISOString();
|
|
36
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Get the path to the initial message file in a thread directory.
|
|
41
|
+
* Supports both new format (<threadId>.md) and legacy format (message.md).
|
|
42
|
+
*/
|
|
43
|
+
async function getInitialMessagePath(threadDir, threadId) {
|
|
44
|
+
// Try new format first: <threadId>.md
|
|
45
|
+
const newPath = join(threadDir, `${threadId}.md`);
|
|
46
|
+
if (await isFile(newPath)) {
|
|
47
|
+
return newPath;
|
|
48
|
+
}
|
|
49
|
+
// Fall back to legacy format: message.md
|
|
50
|
+
const legacyPath = join(threadDir, MESSAGE_FILE_LEGACY);
|
|
51
|
+
if (await isFile(legacyPath)) {
|
|
52
|
+
return legacyPath;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
37
56
|
/**
|
|
38
57
|
* Check if a name is a valid channel name (starts with # or @)
|
|
39
58
|
*/
|
|
@@ -102,25 +121,27 @@ export async function publishMessage(channelsDir, channelName, content, meta) {
|
|
|
102
121
|
const messageId = generateMessageId();
|
|
103
122
|
const messagePath = join(channelsDir, channelName, messageId);
|
|
104
123
|
await mkdir(messagePath, { recursive: true });
|
|
124
|
+
const host = hostname();
|
|
105
125
|
let messageContent = "";
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
126
|
+
const frontmatter = { host };
|
|
127
|
+
if (meta?.from)
|
|
128
|
+
frontmatter.from = meta.from;
|
|
129
|
+
if (meta?.run_id)
|
|
130
|
+
frontmatter.run_id = meta.run_id;
|
|
131
|
+
if (meta?.tags?.length)
|
|
132
|
+
frontmatter.tags = meta.tags;
|
|
133
|
+
if (meta?.thread_id)
|
|
134
|
+
frontmatter.thread_id = meta.thread_id;
|
|
135
|
+
messageContent = `---\n${stringifyYaml(frontmatter)}---\n\n`;
|
|
116
136
|
messageContent += content + "\n";
|
|
117
|
-
|
|
137
|
+
// Write initial message with timestamp filename (uniform with replies)
|
|
138
|
+
await writeFile(join(messagePath, `${messageId}.md`), messageContent, "utf-8");
|
|
118
139
|
return messageId;
|
|
119
140
|
}
|
|
120
141
|
/**
|
|
121
142
|
* Read messages from a channel
|
|
122
143
|
*/
|
|
123
|
-
export async function readChannel(channelsDir, channelName, limit, since) {
|
|
144
|
+
export async function readChannel(channelsDir, channelName, limit, since, threadId) {
|
|
124
145
|
const channelPath = join(channelsDir, channelName);
|
|
125
146
|
if (!(await isDirectory(channelPath))) {
|
|
126
147
|
return [];
|
|
@@ -133,12 +154,31 @@ export async function readChannel(channelsDir, channelName, limit, since) {
|
|
|
133
154
|
if (entry.name.startsWith("_"))
|
|
134
155
|
continue;
|
|
135
156
|
const messageDir = join(channelPath, entry.name);
|
|
136
|
-
const
|
|
137
|
-
|
|
157
|
+
const threadId_local = entry.name;
|
|
158
|
+
// Find initial message file (new or legacy format)
|
|
159
|
+
const messagePath = await getInitialMessagePath(messageDir, threadId_local);
|
|
160
|
+
if (!messagePath)
|
|
138
161
|
continue;
|
|
139
162
|
const messageDate = new Date(entry.name);
|
|
140
163
|
if (since && messageDate < since)
|
|
141
164
|
continue;
|
|
165
|
+
// If filtering by thread, we need to read and check frontmatter first
|
|
166
|
+
if (threadId) {
|
|
167
|
+
try {
|
|
168
|
+
const content = await readFile(messagePath, "utf-8");
|
|
169
|
+
if (content.startsWith("---\n")) {
|
|
170
|
+
const parsed = parseFrontmatter(content);
|
|
171
|
+
if (parsed.frontmatter.thread_id !== threadId)
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
continue; // No frontmatter means no thread_id
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
142
182
|
try {
|
|
143
183
|
const content = await readFile(messagePath, "utf-8");
|
|
144
184
|
let meta = {};
|
|
@@ -156,7 +196,7 @@ export async function readChannel(channelsDir, channelName, limit, since) {
|
|
|
156
196
|
else {
|
|
157
197
|
body = content.trim();
|
|
158
198
|
}
|
|
159
|
-
const replies = await readReplies(messageDir);
|
|
199
|
+
const replies = await readReplies(messageDir, threadId_local);
|
|
160
200
|
messages.push({
|
|
161
201
|
id: entry.name,
|
|
162
202
|
content: body,
|
|
@@ -175,16 +215,20 @@ export async function readChannel(channelsDir, channelName, limit, since) {
|
|
|
175
215
|
return messages;
|
|
176
216
|
}
|
|
177
217
|
/**
|
|
178
|
-
* Read replies for a message
|
|
218
|
+
* Read replies for a message (excluding the initial message)
|
|
179
219
|
*/
|
|
180
|
-
async function readReplies(messageDir) {
|
|
220
|
+
async function readReplies(messageDir, threadId) {
|
|
181
221
|
const entries = await readdir(messageDir, { withFileTypes: true });
|
|
182
222
|
const replies = [];
|
|
223
|
+
// Files to skip: legacy message.md and the initial message (<threadId>.md)
|
|
224
|
+
const initialMessageFile = `${threadId}.md`;
|
|
183
225
|
for (const entry of entries) {
|
|
184
226
|
if (!entry.isFile())
|
|
185
227
|
continue;
|
|
186
|
-
if (entry.name ===
|
|
187
|
-
continue;
|
|
228
|
+
if (entry.name === MESSAGE_FILE_LEGACY)
|
|
229
|
+
continue; // Skip legacy initial
|
|
230
|
+
if (entry.name === initialMessageFile)
|
|
231
|
+
continue; // Skip new-format initial
|
|
188
232
|
if (!entry.name.endsWith(".md"))
|
|
189
233
|
continue;
|
|
190
234
|
const replyId = entry.name.replace(/\.md$/, "");
|
|
@@ -227,17 +271,16 @@ export async function replyToMessage(channelsDir, channelName, messageId, conten
|
|
|
227
271
|
throw new Error(`Message not found: ${channelName}/${messageId}`);
|
|
228
272
|
}
|
|
229
273
|
const replyId = generateMessageId();
|
|
274
|
+
const host = hostname();
|
|
230
275
|
let replyContent = "";
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
replyContent = `---\n${stringifyYaml(frontmatter)}---\n\n`;
|
|
240
|
-
}
|
|
276
|
+
const frontmatter = { host };
|
|
277
|
+
if (meta?.from)
|
|
278
|
+
frontmatter.from = meta.from;
|
|
279
|
+
if (meta?.run_id)
|
|
280
|
+
frontmatter.run_id = meta.run_id;
|
|
281
|
+
if (meta?.tags?.length)
|
|
282
|
+
frontmatter.tags = meta.tags;
|
|
283
|
+
replyContent = `---\n${stringifyYaml(frontmatter)}---\n\n`;
|
|
241
284
|
replyContent += content + "\n";
|
|
242
285
|
await writeFile(join(messagePath, `${replyId}.md`), replyContent, "utf-8");
|
|
243
286
|
return replyId;
|
|
@@ -253,4 +296,152 @@ export async function loadChannelMetadata(channelsDir, channelName) {
|
|
|
253
296
|
const content = await readFile(metadataPath, "utf-8");
|
|
254
297
|
return parseYaml(content);
|
|
255
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* Get the last processed timestamp for a channel
|
|
301
|
+
*/
|
|
302
|
+
export async function getLastProcessedTime(channelsDir, channelName) {
|
|
303
|
+
const lastProcessedPath = join(channelsDir, channelName, LAST_PROCESSED_FILE);
|
|
304
|
+
if (!(await isFile(lastProcessedPath))) {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
const content = await readFile(lastProcessedPath, "utf-8");
|
|
309
|
+
const info = parseYaml(content);
|
|
310
|
+
return new Date(info.last_processed_at);
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Mark a channel as processed at the current time
|
|
318
|
+
*/
|
|
319
|
+
export async function markChannelProcessed(channelsDir, channelName) {
|
|
320
|
+
const channelPath = join(channelsDir, channelName);
|
|
321
|
+
if (!(await isDirectory(channelPath))) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const info = {
|
|
325
|
+
last_processed_at: new Date().toISOString(),
|
|
326
|
+
processed_by: hostname(),
|
|
327
|
+
};
|
|
328
|
+
const lastProcessedPath = join(channelPath, LAST_PROCESSED_FILE);
|
|
329
|
+
await writeFile(lastProcessedPath, stringifyYaml(info), "utf-8");
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Get pending (unprocessed) messages for a DM channel
|
|
333
|
+
* Returns messages newer than the last processed timestamp
|
|
334
|
+
*/
|
|
335
|
+
export async function getPendingMessages(channelsDir, channelName) {
|
|
336
|
+
const channelPath = join(channelsDir, channelName);
|
|
337
|
+
if (!(await isDirectory(channelPath))) {
|
|
338
|
+
return [];
|
|
339
|
+
}
|
|
340
|
+
const lastProcessed = await getLastProcessedTime(channelsDir, channelName);
|
|
341
|
+
const entries = await readdir(channelPath, { withFileTypes: true });
|
|
342
|
+
const pending = [];
|
|
343
|
+
for (const entry of entries) {
|
|
344
|
+
if (!entry.isDirectory())
|
|
345
|
+
continue;
|
|
346
|
+
if (entry.name.startsWith("_"))
|
|
347
|
+
continue;
|
|
348
|
+
// Message ID is an ISO timestamp
|
|
349
|
+
const messageDate = new Date(entry.name);
|
|
350
|
+
if (Number.isNaN(messageDate.getTime()))
|
|
351
|
+
continue;
|
|
352
|
+
// Skip if already processed
|
|
353
|
+
if (lastProcessed && messageDate <= lastProcessed)
|
|
354
|
+
continue;
|
|
355
|
+
const messageDir = join(channelPath, entry.name);
|
|
356
|
+
const threadId = entry.name;
|
|
357
|
+
// Find initial message file (new or legacy format)
|
|
358
|
+
const messagePath = await getInitialMessagePath(messageDir, threadId);
|
|
359
|
+
if (!messagePath)
|
|
360
|
+
continue;
|
|
361
|
+
try {
|
|
362
|
+
const content = await readFile(messagePath, "utf-8");
|
|
363
|
+
let meta = {};
|
|
364
|
+
let body;
|
|
365
|
+
if (content.startsWith("---\n")) {
|
|
366
|
+
try {
|
|
367
|
+
const parsed = parseFrontmatter(content);
|
|
368
|
+
meta = parsed.frontmatter;
|
|
369
|
+
body = parsed.body;
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
body = content;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
body = content.trim();
|
|
377
|
+
}
|
|
378
|
+
pending.push({
|
|
379
|
+
id: entry.name,
|
|
380
|
+
channel: channelName,
|
|
381
|
+
path: messagePath,
|
|
382
|
+
content: body,
|
|
383
|
+
meta,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
// Sort by timestamp ascending (oldest first for processing order)
|
|
391
|
+
return pending.sort((a, b) => a.id.localeCompare(b.id));
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* List all DM channels (@persona) in the channels directory
|
|
395
|
+
*/
|
|
396
|
+
export async function listDMChannels(channelsDir) {
|
|
397
|
+
if (!(await isDirectory(channelsDir))) {
|
|
398
|
+
return [];
|
|
399
|
+
}
|
|
400
|
+
const entries = await readdir(channelsDir, { withFileTypes: true });
|
|
401
|
+
const dmChannels = [];
|
|
402
|
+
for (const entry of entries) {
|
|
403
|
+
if (!entry.isDirectory())
|
|
404
|
+
continue;
|
|
405
|
+
if (entry.name.startsWith("@")) {
|
|
406
|
+
dmChannels.push(entry.name);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return dmChannels.sort();
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Get the workspace directory path for a thread.
|
|
413
|
+
* The workspace is a directory within the thread for working files.
|
|
414
|
+
*
|
|
415
|
+
* @param channelsDir - Base channels directory
|
|
416
|
+
* @param channelName - Channel name (e.g., "#sessions")
|
|
417
|
+
* @param threadId - Thread ID (ISO timestamp)
|
|
418
|
+
* @param create - If true, creates the workspace directory if it doesn't exist
|
|
419
|
+
* @returns Path to the workspace directory, or null if thread doesn't exist
|
|
420
|
+
*/
|
|
421
|
+
export async function getThreadWorkspace(channelsDir, channelName, threadId, create = false) {
|
|
422
|
+
const threadDir = join(channelsDir, channelName, threadId);
|
|
423
|
+
if (!(await isDirectory(threadDir))) {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
const workspacePath = join(threadDir, "workspace");
|
|
427
|
+
if (create) {
|
|
428
|
+
await mkdir(workspacePath, { recursive: true });
|
|
429
|
+
}
|
|
430
|
+
return workspacePath;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Get the thread directory path.
|
|
434
|
+
*
|
|
435
|
+
* @param channelsDir - Base channels directory
|
|
436
|
+
* @param channelName - Channel name (e.g., "#sessions")
|
|
437
|
+
* @param threadId - Thread ID (ISO timestamp)
|
|
438
|
+
* @returns Path to the thread directory, or null if it doesn't exist
|
|
439
|
+
*/
|
|
440
|
+
export async function getThreadPath(channelsDir, channelName, threadId) {
|
|
441
|
+
const threadDir = join(channelsDir, channelName, threadId);
|
|
442
|
+
if (!(await isDirectory(threadDir))) {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
return threadDir;
|
|
446
|
+
}
|
|
256
447
|
//# sourceMappingURL=channel.js.map
|