@ebowwa/daemons 0.5.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 (42) hide show
  1. package/README.md +264 -0
  2. package/dist/bin/discord-cli.js +124118 -0
  3. package/dist/bin/manager.js +143 -0
  4. package/dist/bin/telegram-cli.js +124114 -0
  5. package/dist/index.js +125340 -0
  6. package/package.json +94 -0
  7. package/src/agent.ts +111 -0
  8. package/src/channels/base.ts +573 -0
  9. package/src/channels/discord.ts +306 -0
  10. package/src/channels/index.ts +169 -0
  11. package/src/channels/telegram.ts +315 -0
  12. package/src/daemon.ts +534 -0
  13. package/src/hooks.ts +97 -0
  14. package/src/index.ts +111 -0
  15. package/src/memory.ts +369 -0
  16. package/src/skills/coding/commit.ts +202 -0
  17. package/src/skills/coding/execute-subtask.ts +136 -0
  18. package/src/skills/coding/fix-issues.ts +126 -0
  19. package/src/skills/coding/index.ts +26 -0
  20. package/src/skills/coding/plan-task.ts +158 -0
  21. package/src/skills/coding/quality-check.ts +155 -0
  22. package/src/skills/index.ts +65 -0
  23. package/src/skills/registry.ts +380 -0
  24. package/src/skills/shared/index.ts +21 -0
  25. package/src/skills/shared/reflect.ts +156 -0
  26. package/src/skills/shared/review.ts +201 -0
  27. package/src/skills/shared/trajectory.ts +319 -0
  28. package/src/skills/trading/analyze-market.ts +144 -0
  29. package/src/skills/trading/check-risk.ts +176 -0
  30. package/src/skills/trading/execute-trade.ts +185 -0
  31. package/src/skills/trading/generate-signal.ts +160 -0
  32. package/src/skills/trading/index.ts +26 -0
  33. package/src/skills/trading/monitor-position.ts +179 -0
  34. package/src/skills/types.ts +235 -0
  35. package/src/skills/workflows.ts +340 -0
  36. package/src/state.ts +77 -0
  37. package/src/tools.ts +134 -0
  38. package/src/types.ts +314 -0
  39. package/src/workflow.ts +341 -0
  40. package/src/workflows/coding.ts +580 -0
  41. package/src/workflows/index.ts +61 -0
  42. package/src/workflows/trading.ts +608 -0
@@ -0,0 +1,340 @@
1
+ /**
2
+ * GLM Daemon - Skill-Based Workflows
3
+ *
4
+ * Workflow definitions using skill sequences.
5
+ * Replaces the old class-based workflow system.
6
+ */
7
+
8
+ import type { SkillWorkflowConfig, SkillTransition } from "./types.js";
9
+
10
+ // ============================================
11
+ // CODING WORKFLOW
12
+ // ============================================
13
+
14
+ const codingTransitions: SkillTransition[] = [
15
+ // Planning -> Execution
16
+ { from: "/plan-task", to: "/execute-subtask", priority: 100 },
17
+
18
+ // Execution flow
19
+ { from: "/execute-subtask", to: "/quality-check",
20
+ condition: (ctx) => {
21
+ const completed = ctx.state.slam.completedSubtasks?.length || 0;
22
+ const total = ctx.state.slam.subtasks?.length || 0;
23
+ return total > 0 && completed >= total;
24
+ },
25
+ priority: 90 },
26
+ { from: "/execute-subtask", to: "/execute-subtask", priority: 50 },
27
+
28
+ // Quality check flow
29
+ { from: "/quality-check", to: "/fix-issues",
30
+ condition: (_, result) => (result.data as Record<string, unknown>)?.hasIssues === true,
31
+ priority: 90 },
32
+ { from: "/quality-check", to: "/review", priority: 80 },
33
+
34
+ // Review flow
35
+ { from: "/review", to: "/fix-issues",
36
+ condition: (_, result) => (result.data as Record<string, unknown>)?.approved === false,
37
+ priority: 90 },
38
+ { from: "/review", to: "/commit", priority: 80 },
39
+
40
+ // Fix flow
41
+ { from: "/fix-issues", to: "/quality-check", priority: 100 },
42
+
43
+ // Commit -> Complete
44
+ { from: "/commit", to: "/complete", priority: 100 },
45
+
46
+ // Reflection (can happen from any skill)
47
+ { from: "*", to: "/reflect",
48
+ condition: (ctx) => {
49
+ const enabled = ctx.state.reflection?.toolCount !== undefined;
50
+ const limit = 50; // Default tool limit
51
+ const count = ctx.state.reflection?.toolCount || 0;
52
+ return enabled && count >= limit;
53
+ },
54
+ priority: 200 },
55
+ { from: "/reflect", to: "/execute-subtask", priority: 100 },
56
+ ];
57
+
58
+ export const codingWorkflow: SkillWorkflowConfig = {
59
+ id: "coding",
60
+ name: "Coding Workflow",
61
+ description: "Software development workflow with planning, execution, and quality checks",
62
+
63
+ skills: [
64
+ "/plan-task",
65
+ "/execute-subtask",
66
+ "/reflect",
67
+ "/quality-check",
68
+ "/review",
69
+ "/fix-issues",
70
+ "/commit",
71
+ ],
72
+
73
+ initialSkill: "/plan-task",
74
+ terminalSkills: ["/commit", "/complete"],
75
+
76
+ transitions: codingTransitions,
77
+
78
+ systemPromptExtensions: `You are a software development assistant.
79
+ Use tools when available. Be thorough but efficient.
80
+ Focus on code changes, testing, and verification.`,
81
+
82
+ reflectionSkill: "/reflect",
83
+ };
84
+
85
+ // ============================================
86
+ // TRADING WORKFLOW
87
+ // ============================================
88
+
89
+ const tradingTransitions: SkillTransition[] = [
90
+ // Analysis flow
91
+ { from: "/analyze-market", to: "/generate-signal", priority: 100 },
92
+
93
+ // Signal flow
94
+ { from: "/generate-signal", to: "/check-risk",
95
+ condition: (_, result) => (result.data as Record<string, unknown>)?.direction !== "none",
96
+ priority: 100 },
97
+ { from: "/generate-signal", to: "/analyze-market",
98
+ condition: (_, result) => (result.data as Record<string, unknown>)?.direction === "none",
99
+ priority: 90 },
100
+
101
+ // Risk check flow
102
+ { from: "/check-risk", to: "/execute-trade",
103
+ condition: (_, result) => (result.data as Record<string, unknown>)?.approved === true,
104
+ priority: 100 },
105
+ { from: "/check-risk", to: "/analyze-market",
106
+ condition: (_, result) => (result.data as Record<string, unknown>)?.approved === false,
107
+ priority: 90 },
108
+
109
+ // Execution -> Monitoring
110
+ { from: "/execute-trade", to: "/monitor-position",
111
+ condition: (_, result) => result.success && (result.data as Record<string, unknown>)?.status !== "failed",
112
+ priority: 100 },
113
+ { from: "/execute-trade", to: "/analyze-market",
114
+ condition: (_, result) => !result.success,
115
+ priority: 90 },
116
+
117
+ // Monitoring flow
118
+ { from: "/monitor-position", to: "/complete",
119
+ condition: (ctx) => ctx.custom.targetReached === true || ctx.custom.stopLoss === true,
120
+ priority: 100 },
121
+ { from: "/monitor-position", to: "/analyze-market",
122
+ condition: (ctx) => ctx.custom.positionClosed === true,
123
+ priority: 90 },
124
+ { from: "/monitor-position", to: "/reflect",
125
+ condition: (ctx) => ctx.state.iteration > 0 && ctx.state.iteration % 10 === 0,
126
+ priority: 80 },
127
+ { from: "/monitor-position", to: "/monitor-position", priority: 50 },
128
+
129
+ // Reflection
130
+ { from: "/reflect", to: "/monitor-position", priority: 100 },
131
+ ];
132
+
133
+ export const tradingWorkflow: SkillWorkflowConfig = {
134
+ id: "trading",
135
+ name: "Trading Workflow",
136
+ description: "Trading workflow with analysis, signal generation, risk management, and execution",
137
+
138
+ skills: [
139
+ "/analyze-market",
140
+ "/generate-signal",
141
+ "/check-risk",
142
+ "/execute-trade",
143
+ "/monitor-position",
144
+ "/reflect",
145
+ ],
146
+
147
+ initialSkill: "/analyze-market",
148
+ terminalSkills: ["/complete"],
149
+
150
+ transitions: tradingTransitions,
151
+
152
+ skillParams: {
153
+ "/check-risk": {
154
+ maxRiskPerTrade: 2,
155
+ minRiskReward: 1.5,
156
+ },
157
+ "/execute-trade": {
158
+ paper: true, // Default to paper trading
159
+ },
160
+ },
161
+
162
+ systemPromptExtensions: `You are a trading assistant.
163
+ Follow risk management rules strictly.
164
+ Never exceed position size limits.
165
+ Always use stop-loss orders.
166
+ No git operations are needed - focus on market execution.`,
167
+
168
+ reflectionSkill: "/reflect",
169
+ };
170
+
171
+ // ============================================
172
+ // WORKFLOW REGISTRY
173
+ // ============================================
174
+
175
+ /**
176
+ * Simple workflow registry for skill-based workflows
177
+ */
178
+ class WorkflowConfigRegistry {
179
+ private workflows: Map<string, SkillWorkflowConfig> = new Map();
180
+ private defaultWorkflow: string | null = null;
181
+
182
+ register(config: SkillWorkflowConfig, setDefault = false): void {
183
+ this.workflows.set(config.id, config);
184
+ if (setDefault || this.workflows.size === 1) {
185
+ this.defaultWorkflow = config.id;
186
+ }
187
+ }
188
+
189
+ get(id: string): SkillWorkflowConfig | undefined {
190
+ return this.workflows.get(id);
191
+ }
192
+
193
+ getDefault(): SkillWorkflowConfig | undefined {
194
+ if (!this.defaultWorkflow) return undefined;
195
+ return this.workflows.get(this.defaultWorkflow);
196
+ }
197
+
198
+ list(): Array<{ id: string; name: string; description: string }> {
199
+ return Array.from(this.workflows.values()).map((w) => ({
200
+ id: w.id,
201
+ name: w.name,
202
+ description: w.description,
203
+ }));
204
+ }
205
+ }
206
+
207
+ export const workflowConfigRegistry = new WorkflowConfigRegistry();
208
+
209
+ // Register built-in workflows
210
+ workflowConfigRegistry.register(codingWorkflow, true);
211
+ workflowConfigRegistry.register(tradingWorkflow);
212
+
213
+ // ============================================
214
+ // WORKFLOW EXECUTOR
215
+ // ============================================
216
+
217
+ import type { SkillContext, SkillResult } from "./types.js";
218
+ import { skillRegistry } from "./registry.js";
219
+
220
+ /**
221
+ * Execute a skill-based workflow
222
+ */
223
+ export async function executeWorkflowSkill(
224
+ workflow: SkillWorkflowConfig,
225
+ context: SkillContext
226
+ ): Promise<SkillResult> {
227
+ const currentSkillId = context.state.slam.phase as string;
228
+
229
+ // Get skill params from workflow config
230
+ const skillParams = workflow.skillParams?.[currentSkillId];
231
+
232
+ // Execute the current skill
233
+ const result = await skillRegistry.execute(
234
+ currentSkillId,
235
+ context,
236
+ skillParams
237
+ );
238
+
239
+ if (!result.success) {
240
+ return result;
241
+ }
242
+
243
+ // Check if skill specified next skill
244
+ if (result.nextSkill) {
245
+ return {
246
+ ...result,
247
+ stateUpdates: {
248
+ ...result.stateUpdates,
249
+ slam: {
250
+ ...context.state.slam,
251
+ ...result.stateUpdates?.slam,
252
+ phase: result.nextSkill,
253
+ },
254
+ },
255
+ };
256
+ }
257
+
258
+ // Find next skill using transitions
259
+ const nextSkill = await findNextSkill(workflow, context, result);
260
+
261
+ return {
262
+ ...result,
263
+ stateUpdates: {
264
+ ...result.stateUpdates,
265
+ slam: {
266
+ ...context.state.slam,
267
+ ...result.stateUpdates?.slam,
268
+ phase: nextSkill,
269
+ },
270
+ },
271
+ };
272
+ }
273
+
274
+ /**
275
+ * Find the next skill based on transitions
276
+ */
277
+ async function findNextSkill(
278
+ workflow: SkillWorkflowConfig,
279
+ context: SkillContext,
280
+ result: SkillResult
281
+ ): Promise<string> {
282
+ const currentSkillId = context.state.slam.phase as string;
283
+
284
+ // Sort transitions by priority (descending)
285
+ const sortedTransitions = [...workflow.transitions].sort(
286
+ (a, b) => (b.priority || 0) - (a.priority || 0)
287
+ );
288
+
289
+ // Check each transition
290
+ for (const transition of sortedTransitions) {
291
+ // Check if transition matches current skill (or wildcard)
292
+ if (transition.from !== "*" && transition.from !== currentSkillId) {
293
+ continue;
294
+ }
295
+
296
+ // Check condition if present
297
+ if (transition.condition) {
298
+ try {
299
+ const shouldTransition = await transition.condition(context, result);
300
+ if (!shouldTransition) continue;
301
+ } catch {
302
+ continue;
303
+ }
304
+ }
305
+
306
+ // Return target skill
307
+ return transition.to;
308
+ }
309
+
310
+ // Fall back to default order
311
+ const currentIndex = workflow.skills.indexOf(currentSkillId);
312
+ if (currentIndex >= 0 && currentIndex < workflow.skills.length - 1) {
313
+ return workflow.skills[currentIndex + 1];
314
+ }
315
+
316
+ // Stay on current skill if no transition matches
317
+ return currentSkillId;
318
+ }
319
+
320
+ /**
321
+ * Check if workflow is complete
322
+ */
323
+ export async function isWorkflowComplete(
324
+ workflow: SkillWorkflowConfig,
325
+ context: SkillContext
326
+ ): Promise<boolean> {
327
+ const currentPhase = context.state.slam.phase as string;
328
+
329
+ // Check if in terminal skill
330
+ if (workflow.terminalSkills.includes(currentPhase)) {
331
+ return true;
332
+ }
333
+
334
+ // Check custom completion condition
335
+ if (workflow.isComplete) {
336
+ return await workflow.isComplete(context);
337
+ }
338
+
339
+ return false;
340
+ }
package/src/state.ts ADDED
@@ -0,0 +1,77 @@
1
+ /**
2
+ * GLM Daemon - State Manager
3
+ *
4
+ * Manages SLAM state persistence to disk.
5
+ */
6
+
7
+ import { promises as fsp } from "fs";
8
+ import type { GLMDaemonState } from "./types.js";
9
+
10
+ export class StateManager {
11
+ private statePath: string;
12
+
13
+ constructor(statePath: string) {
14
+ this.statePath = statePath;
15
+ }
16
+
17
+ /**
18
+ * Load state from disk
19
+ */
20
+ async load(): Promise<GLMDaemonState | null> {
21
+ try {
22
+ const content = await fsp.readFile(this.statePath, "utf-8");
23
+ return JSON.parse(content) as GLMDaemonState;
24
+ } catch {
25
+ // File doesn't exist or invalid JSON
26
+ return null;
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Save state to disk
32
+ */
33
+ async save(state: GLMDaemonState): Promise<void> {
34
+ await fsp.writeFile(
35
+ this.statePath,
36
+ JSON.stringify(state, null, 2),
37
+ "utf-8"
38
+ );
39
+ }
40
+
41
+ /**
42
+ * Delete state file
43
+ */
44
+ async delete(): Promise<void> {
45
+ try {
46
+ await fsp.unlink(this.statePath);
47
+ } catch {
48
+ // File doesn't exist, ignore
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Check if state file exists
54
+ */
55
+ async exists(): Promise<boolean> {
56
+ try {
57
+ await fsp.access(this.statePath);
58
+ return true;
59
+ } catch {
60
+ return false;
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Update a portion of state (merge)
66
+ */
67
+ async update(updates: Partial<GLMDaemonState>): Promise<GLMDaemonState> {
68
+ const current = await this.load();
69
+ if (!current) {
70
+ throw new Error("No existing state to update");
71
+ }
72
+
73
+ const updated = { ...current, ...updates };
74
+ await this.save(updated);
75
+ return updated;
76
+ }
77
+ }
package/src/tools.ts ADDED
@@ -0,0 +1,134 @@
1
+ /**
2
+ * GLM Daemon - Tool Bridge
3
+ *
4
+ * Connects GLM agents to MCP servers for tool access.
5
+ */
6
+
7
+ import type { GLMToolsConfig } from "./types.js";
8
+
9
+ export class ToolBridge {
10
+ private config: GLMToolsConfig;
11
+ private availableTools: string[] = [];
12
+ private toolTimeout: number;
13
+ private maxRetries: number;
14
+
15
+ constructor(config: GLMToolsConfig = {}) {
16
+ this.config = config;
17
+ this.toolTimeout = config.toolTimeout || 30000;
18
+ this.maxRetries = config.maxRetries || 3;
19
+ }
20
+
21
+ /**
22
+ * Get list of available tools
23
+ */
24
+ getAvailableTools(): string[] {
25
+ return this.availableTools;
26
+ }
27
+
28
+ /**
29
+ * Set available tools (from MCP discovery)
30
+ */
31
+ setAvailableTools(tools: string[]): void {
32
+ this.availableTools = tools;
33
+ }
34
+
35
+ /**
36
+ * Check if a tool is allowed
37
+ */
38
+ isToolAllowed(toolName: string): boolean {
39
+ // Check blacklist first
40
+ if (this.config.blockedTools?.includes(toolName)) {
41
+ return false;
42
+ }
43
+
44
+ // If whitelist exists, check it
45
+ if (this.config.allowedTools && this.config.allowedTools.length > 0) {
46
+ return this.config.allowedTools.includes(toolName);
47
+ }
48
+
49
+ // Default: allow if not blocked
50
+ return true;
51
+ }
52
+
53
+ /**
54
+ * Execute a tool with timeout and retry
55
+ */
56
+ async executeTool(
57
+ toolName: string,
58
+ args: Record<string, unknown>
59
+ ): Promise<{ success: boolean; result?: unknown; error?: string }> {
60
+ if (!this.isToolAllowed(toolName)) {
61
+ return {
62
+ success: false,
63
+ error: `Tool not allowed: ${toolName}`,
64
+ };
65
+ }
66
+
67
+ let lastError: Error | null = null;
68
+
69
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) {
70
+ try {
71
+ const result = await this.executeWithTimeout(toolName, args);
72
+ return { success: true, result };
73
+ } catch (error) {
74
+ lastError = error as Error;
75
+ // Wait before retry (exponential backoff)
76
+ if (attempt < this.maxRetries - 1) {
77
+ await new Promise((resolve) =>
78
+ setTimeout(resolve, Math.pow(2, attempt) * 1000)
79
+ );
80
+ }
81
+ }
82
+ }
83
+
84
+ return {
85
+ success: false,
86
+ error: lastError?.message || "Unknown error",
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Execute tool with timeout
92
+ */
93
+ private async executeWithTimeout(
94
+ toolName: string,
95
+ args: Record<string, unknown>
96
+ ): Promise<unknown> {
97
+ return new Promise((resolve, reject) => {
98
+ const timer = setTimeout(() => {
99
+ reject(new Error(`Tool timeout: ${toolName}`));
100
+ }, this.toolTimeout);
101
+
102
+ // TODO: Actually call MCP tool here
103
+ // For now, this is a placeholder
104
+ setTimeout(() => {
105
+ clearTimeout(timer);
106
+ resolve(`Executed ${toolName} with ${JSON.stringify(args)}`);
107
+ }, 100);
108
+ });
109
+ }
110
+
111
+ /**
112
+ * Discover tools from MCP servers
113
+ */
114
+ async discoverMCPTools(): Promise<void> {
115
+ if (!this.config.enableMCP) {
116
+ return;
117
+ }
118
+
119
+ // TODO: Query MCP servers for available tools
120
+ // For now, add common tools
121
+ const commonTools = [
122
+ "Read",
123
+ "Write",
124
+ "Edit",
125
+ "Grep",
126
+ "Glob",
127
+ "Bash",
128
+ "git_status",
129
+ "git_commit",
130
+ ];
131
+
132
+ this.setAvailableTools(commonTools);
133
+ }
134
+ }