@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.
- package/README.md +264 -0
- package/dist/bin/discord-cli.js +124118 -0
- package/dist/bin/manager.js +143 -0
- package/dist/bin/telegram-cli.js +124114 -0
- package/dist/index.js +125340 -0
- package/package.json +94 -0
- package/src/agent.ts +111 -0
- package/src/channels/base.ts +573 -0
- package/src/channels/discord.ts +306 -0
- package/src/channels/index.ts +169 -0
- package/src/channels/telegram.ts +315 -0
- package/src/daemon.ts +534 -0
- package/src/hooks.ts +97 -0
- package/src/index.ts +111 -0
- package/src/memory.ts +369 -0
- package/src/skills/coding/commit.ts +202 -0
- package/src/skills/coding/execute-subtask.ts +136 -0
- package/src/skills/coding/fix-issues.ts +126 -0
- package/src/skills/coding/index.ts +26 -0
- package/src/skills/coding/plan-task.ts +158 -0
- package/src/skills/coding/quality-check.ts +155 -0
- package/src/skills/index.ts +65 -0
- package/src/skills/registry.ts +380 -0
- package/src/skills/shared/index.ts +21 -0
- package/src/skills/shared/reflect.ts +156 -0
- package/src/skills/shared/review.ts +201 -0
- package/src/skills/shared/trajectory.ts +319 -0
- package/src/skills/trading/analyze-market.ts +144 -0
- package/src/skills/trading/check-risk.ts +176 -0
- package/src/skills/trading/execute-trade.ts +185 -0
- package/src/skills/trading/generate-signal.ts +160 -0
- package/src/skills/trading/index.ts +26 -0
- package/src/skills/trading/monitor-position.ts +179 -0
- package/src/skills/types.ts +235 -0
- package/src/skills/workflows.ts +340 -0
- package/src/state.ts +77 -0
- package/src/tools.ts +134 -0
- package/src/types.ts +314 -0
- package/src/workflow.ts +341 -0
- package/src/workflows/coding.ts +580 -0
- package/src/workflows/index.ts +61 -0
- package/src/workflows/trading.ts +608 -0
package/src/daemon.ts
ADDED
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GLM Daemon - Core Daemon
|
|
3
|
+
*
|
|
4
|
+
* Main orchestrator for autonomous GLM 4.7 agents.
|
|
5
|
+
* Implements SLAM pattern: State → Loop → Action → Memory
|
|
6
|
+
*
|
|
7
|
+
* Now skill-based - uses composable skills instead of hardcoded phases.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { promises as fsp } from "fs";
|
|
11
|
+
import { join } from "path";
|
|
12
|
+
import type {
|
|
13
|
+
GLMDaemonConfig,
|
|
14
|
+
GLMDaemonState,
|
|
15
|
+
GLMDaemonStatus,
|
|
16
|
+
} from "./types.js";
|
|
17
|
+
import { GLMAgent } from "./agent.js";
|
|
18
|
+
import { HookSystem } from "./hooks.js";
|
|
19
|
+
import { ToolBridge } from "./tools.js";
|
|
20
|
+
import { StateManager } from "./state.js";
|
|
21
|
+
import { Team } from "@ebowwa/teammates";
|
|
22
|
+
import { GLMClient } from "@ebowwa/ai";
|
|
23
|
+
import {
|
|
24
|
+
type SkillWorkflowConfig,
|
|
25
|
+
type SkillContext,
|
|
26
|
+
codingWorkflow,
|
|
27
|
+
workflowConfigRegistry,
|
|
28
|
+
executeWorkflowSkill,
|
|
29
|
+
isWorkflowComplete,
|
|
30
|
+
skillRegistry,
|
|
31
|
+
} from "./skills/index.js";
|
|
32
|
+
|
|
33
|
+
// Re-export types for external use
|
|
34
|
+
export type { GLMDaemonConfig, GLMDaemonState, GLMDaemonStatus } from "./types.js";
|
|
35
|
+
|
|
36
|
+
const STATE_FILE = ".glm-daemon.json";
|
|
37
|
+
const SETTINGS_FILE = ".claude/settings.local.json";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Extended daemon config with skill-based workflow support
|
|
41
|
+
*/
|
|
42
|
+
export interface GLMDaemonConfigWithWorkflow extends GLMDaemonConfig {
|
|
43
|
+
/** Workflow ID to use (defaults to "coding") */
|
|
44
|
+
workflowId?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class GLMDaemon {
|
|
48
|
+
private config: GLMDaemonConfigWithWorkflow;
|
|
49
|
+
private state: GLMDaemonState;
|
|
50
|
+
private statePath: string;
|
|
51
|
+
private agent: GLMAgent;
|
|
52
|
+
private hooks: HookSystem;
|
|
53
|
+
private tools: ToolBridge;
|
|
54
|
+
private stateManager: StateManager;
|
|
55
|
+
private team: Team | null = null;
|
|
56
|
+
private subagents: Map<string, GLMAgent> = new Map();
|
|
57
|
+
private running = false;
|
|
58
|
+
private client: GLMClient;
|
|
59
|
+
private workflow: SkillWorkflowConfig;
|
|
60
|
+
private customContext: Record<string, unknown> = {};
|
|
61
|
+
|
|
62
|
+
constructor(config: GLMDaemonConfigWithWorkflow) {
|
|
63
|
+
this.config = config;
|
|
64
|
+
this.statePath = join(config.cwd, STATE_FILE);
|
|
65
|
+
this.client = new GLMClient();
|
|
66
|
+
|
|
67
|
+
// Initialize workflow from registry
|
|
68
|
+
const workflowId = config.workflowId || "coding";
|
|
69
|
+
const foundWorkflow = workflowConfigRegistry.get(workflowId);
|
|
70
|
+
if (!foundWorkflow) {
|
|
71
|
+
throw new Error(`Workflow not found: ${workflowId}. Available: ${workflowConfigRegistry.list().map(w => w.id).join(", ")}`);
|
|
72
|
+
}
|
|
73
|
+
this.workflow = foundWorkflow;
|
|
74
|
+
|
|
75
|
+
console.log(`[GLMDaemon] Using workflow: ${this.workflow.name}`);
|
|
76
|
+
|
|
77
|
+
// Initialize state
|
|
78
|
+
this.state = this.initializeState();
|
|
79
|
+
|
|
80
|
+
// Initialize components
|
|
81
|
+
this.hooks = new HookSystem(config.hooks || {});
|
|
82
|
+
this.tools = new ToolBridge(config.tools || {});
|
|
83
|
+
this.stateManager = new StateManager(this.statePath);
|
|
84
|
+
|
|
85
|
+
// Initialize main agent
|
|
86
|
+
this.agent = new GLMAgent({
|
|
87
|
+
agentId: "glm-daemon",
|
|
88
|
+
name: "GLM Daemon",
|
|
89
|
+
prompt: this.buildSystemPrompt(),
|
|
90
|
+
model: config.model,
|
|
91
|
+
tools: this.tools.getAvailableTools(),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Log available skills
|
|
95
|
+
console.log(`[GLMDaemon] Available skills: ${skillRegistry.listIds().length}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Initialize daemon state
|
|
100
|
+
*/
|
|
101
|
+
private initializeState(): GLMDaemonState {
|
|
102
|
+
const now = new Date().toISOString();
|
|
103
|
+
const initialSkill = this.workflow.initialSkill;
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
prompt: "", // Will be set when starting
|
|
107
|
+
promise: this.config.completionPromise || "TASK_COMPLETE",
|
|
108
|
+
iteration: 0,
|
|
109
|
+
maxIterations: this.config.maxIterations || 0,
|
|
110
|
+
startTime: now,
|
|
111
|
+
lastUpdate: now,
|
|
112
|
+
tokens: {
|
|
113
|
+
totalInput: 0,
|
|
114
|
+
totalOutput: 0,
|
|
115
|
+
byIteration: [],
|
|
116
|
+
},
|
|
117
|
+
filesChanged: [],
|
|
118
|
+
workMemory: {
|
|
119
|
+
completedFiles: [],
|
|
120
|
+
fileChecksums: {},
|
|
121
|
+
},
|
|
122
|
+
slam: {
|
|
123
|
+
enabled: true,
|
|
124
|
+
phase: initialSkill, // Now uses skill ID as phase
|
|
125
|
+
state: {
|
|
126
|
+
currentTask: "",
|
|
127
|
+
beliefs: {},
|
|
128
|
+
goals: [this.config.completionPromise || "TASK_COMPLETE"],
|
|
129
|
+
},
|
|
130
|
+
subtasks: [],
|
|
131
|
+
currentSubtask: null,
|
|
132
|
+
completedSubtasks: [],
|
|
133
|
+
memory: {
|
|
134
|
+
actionsTaken: [],
|
|
135
|
+
outcomes: {},
|
|
136
|
+
patterns: {},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
git: {
|
|
140
|
+
enabled: this.config.autoCommit || this.config.autoPR || false,
|
|
141
|
+
autoCommit: this.config.autoCommit || false,
|
|
142
|
+
autoPR: this.config.autoPR || false,
|
|
143
|
+
baseBranch: this.config.baseBranch || "main",
|
|
144
|
+
branchName: "",
|
|
145
|
+
branchCreated: false,
|
|
146
|
+
currentCommit: "",
|
|
147
|
+
},
|
|
148
|
+
reflection: {
|
|
149
|
+
toolCount: 0,
|
|
150
|
+
totalToolCount: 0,
|
|
151
|
+
checkpointCount: 0,
|
|
152
|
+
lastReflection: null,
|
|
153
|
+
summaries: [],
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Build system prompt using workflow config
|
|
160
|
+
*/
|
|
161
|
+
private buildSystemPrompt(): string {
|
|
162
|
+
const currentSkillId = this.state.slam.phase;
|
|
163
|
+
const currentSkill = skillRegistry.get(currentSkillId);
|
|
164
|
+
|
|
165
|
+
let prompt = `You are an autonomous AI agent.
|
|
166
|
+
|
|
167
|
+
Your task: ${this.state.prompt}
|
|
168
|
+
|
|
169
|
+
Workflow: ${this.workflow.name}
|
|
170
|
+
Available skills in this workflow:
|
|
171
|
+
${this.workflow.skills.map(s => `- ${s}`).join("\n")}
|
|
172
|
+
|
|
173
|
+
Current skill: ${currentSkill?.name || currentSkillId}
|
|
174
|
+
${currentSkill?.description ? `Description: ${currentSkill.description}` : ""}
|
|
175
|
+
|
|
176
|
+
Iteration: ${this.state.iteration}
|
|
177
|
+
`;
|
|
178
|
+
|
|
179
|
+
if (this.workflow.systemPromptExtensions) {
|
|
180
|
+
prompt += `\n${this.workflow.systemPromptExtensions}`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return prompt;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Create skill execution context
|
|
188
|
+
*/
|
|
189
|
+
private createSkillContext(): SkillContext {
|
|
190
|
+
return {
|
|
191
|
+
state: this.state,
|
|
192
|
+
agent: this.agent,
|
|
193
|
+
custom: this.customContext,
|
|
194
|
+
incrementToolCount: () => this.incrementToolCount(),
|
|
195
|
+
executeHook: (event: string, ...args: unknown[]) => this.hooks.execute(event, ...args),
|
|
196
|
+
callSkill: async (id: string, params?: unknown) => {
|
|
197
|
+
return skillRegistry.execute(id, this.createSkillContext(), params);
|
|
198
|
+
},
|
|
199
|
+
workflowConfig: this.workflow.skillParams,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Start the daemon with a prompt
|
|
205
|
+
*/
|
|
206
|
+
async start(prompt: string): Promise<string> {
|
|
207
|
+
if (this.running) {
|
|
208
|
+
throw new Error("Daemon already running");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
this.state.prompt = prompt;
|
|
212
|
+
this.state.slam.state.currentTask = prompt;
|
|
213
|
+
this.running = true;
|
|
214
|
+
|
|
215
|
+
// Update agent prompt
|
|
216
|
+
this.agent = new GLMAgent({
|
|
217
|
+
agentId: "glm-daemon",
|
|
218
|
+
name: "GLM Daemon",
|
|
219
|
+
prompt: this.buildSystemPrompt(),
|
|
220
|
+
model: this.config.model,
|
|
221
|
+
tools: this.tools.getAvailableTools(),
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Initialize team for multi-agent coordination
|
|
225
|
+
this.team = await Team.create({
|
|
226
|
+
name: this.config.teamName,
|
|
227
|
+
description: `GLM daemon for: ${prompt.substring(0, 50)}...`,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Run session start hook
|
|
231
|
+
await this.hooks.execute("onSessionStart", this.state);
|
|
232
|
+
|
|
233
|
+
// Save initial state
|
|
234
|
+
await this.stateManager.save(this.state);
|
|
235
|
+
|
|
236
|
+
console.log(`[GLMDaemon] Starting with skill: ${this.state.slam.phase}`);
|
|
237
|
+
|
|
238
|
+
// Start main loop
|
|
239
|
+
this.runLoop().catch((error) => {
|
|
240
|
+
console.error("[GLMDaemon] Loop error:", error);
|
|
241
|
+
this.running = false;
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
return this.config.teamName;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Main daemon loop (SLAM pattern with skill execution)
|
|
249
|
+
*/
|
|
250
|
+
private async runLoop(): Promise<void> {
|
|
251
|
+
while (this.running) {
|
|
252
|
+
// Check completion condition
|
|
253
|
+
if (await this.checkCompletion()) {
|
|
254
|
+
await this.complete();
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Check iteration limit
|
|
259
|
+
if (
|
|
260
|
+
this.state.maxIterations > 0 &&
|
|
261
|
+
this.state.iteration >= this.state.maxIterations
|
|
262
|
+
) {
|
|
263
|
+
console.log(`[GLMDaemon] Max iterations reached: ${this.state.maxIterations}`);
|
|
264
|
+
await this.stop();
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Run iteration start hook
|
|
269
|
+
await this.hooks.execute("onIterationStart", this.state.iteration);
|
|
270
|
+
|
|
271
|
+
// Execute current skill
|
|
272
|
+
await this.executeSkill();
|
|
273
|
+
|
|
274
|
+
// Update state
|
|
275
|
+
this.state.iteration++;
|
|
276
|
+
this.state.lastUpdate = new Date().toISOString();
|
|
277
|
+
|
|
278
|
+
// Save state
|
|
279
|
+
await this.stateManager.save(this.state);
|
|
280
|
+
|
|
281
|
+
// Run iteration end hook
|
|
282
|
+
await this.hooks.execute("onIterationEnd", this.state.iteration, null);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Execute current skill via skill registry
|
|
288
|
+
*/
|
|
289
|
+
private async executeSkill(): Promise<void> {
|
|
290
|
+
const currentSkillId = this.state.slam.phase;
|
|
291
|
+
console.log(`[GLMDaemon] Executing skill: ${currentSkillId}`);
|
|
292
|
+
|
|
293
|
+
// Create skill context
|
|
294
|
+
const context = this.createSkillContext();
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
// Execute via skill-based workflow
|
|
298
|
+
const result = await executeWorkflowSkill(this.workflow, context);
|
|
299
|
+
|
|
300
|
+
// Update custom context
|
|
301
|
+
if (result.contextUpdates) {
|
|
302
|
+
this.customContext = { ...this.customContext, ...result.contextUpdates };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Apply state updates from result
|
|
306
|
+
if (result.stateUpdates) {
|
|
307
|
+
this.applyStateUpdates(result.stateUpdates);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Update phase from result (skill transition)
|
|
311
|
+
if (result.stateUpdates?.slam?.phase) {
|
|
312
|
+
const newPhase = result.stateUpdates.slam.phase;
|
|
313
|
+
if (newPhase !== currentSkillId) {
|
|
314
|
+
console.log(`[GLMDaemon] Skill transition: ${currentSkillId} -> ${newPhase}`);
|
|
315
|
+
this.state.slam.phase = newPhase;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Stop loop if requested
|
|
320
|
+
if (!result.success && result.error) {
|
|
321
|
+
console.error(`[GLMDaemon] Skill error:`, result.error);
|
|
322
|
+
}
|
|
323
|
+
} catch (error) {
|
|
324
|
+
console.error(`[GLMDaemon] Skill execution failed:`, error);
|
|
325
|
+
throw error;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Apply state updates from skill result
|
|
331
|
+
*/
|
|
332
|
+
private applyStateUpdates(updates: Partial<GLMDaemonState>): void {
|
|
333
|
+
// Deep merge slam updates
|
|
334
|
+
if (updates.slam) {
|
|
335
|
+
this.state.slam = {
|
|
336
|
+
...this.state.slam,
|
|
337
|
+
...updates.slam,
|
|
338
|
+
state: {
|
|
339
|
+
...this.state.slam.state,
|
|
340
|
+
...(updates.slam.state || {}),
|
|
341
|
+
},
|
|
342
|
+
memory: {
|
|
343
|
+
...this.state.slam.memory,
|
|
344
|
+
...(updates.slam.memory || {}),
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Apply other updates
|
|
350
|
+
if (updates.filesChanged) {
|
|
351
|
+
this.state.filesChanged = updates.filesChanged;
|
|
352
|
+
}
|
|
353
|
+
if (updates.workMemory) {
|
|
354
|
+
this.state.workMemory = updates.workMemory;
|
|
355
|
+
}
|
|
356
|
+
if (updates.git) {
|
|
357
|
+
this.state.git = { ...this.state.git, ...updates.git };
|
|
358
|
+
}
|
|
359
|
+
if (updates.reflection) {
|
|
360
|
+
this.state.reflection = { ...this.state.reflection, ...updates.reflection };
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Increment tool count and check for reflection trigger
|
|
366
|
+
*/
|
|
367
|
+
incrementToolCount(): boolean {
|
|
368
|
+
const reflectionConfig = this.config.reflection || {
|
|
369
|
+
enabled: false,
|
|
370
|
+
toolLimit: 0,
|
|
371
|
+
autoContinue: true,
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
this.state.reflection.toolCount++;
|
|
375
|
+
this.state.reflection.totalToolCount++;
|
|
376
|
+
|
|
377
|
+
// Check if we hit the limit
|
|
378
|
+
if (reflectionConfig.enabled && reflectionConfig.toolLimit > 0 &&
|
|
379
|
+
this.state.reflection.toolCount >= reflectionConfig.toolLimit) {
|
|
380
|
+
// Store current phase to return after reflection
|
|
381
|
+
this.state.slam.previousPhase = this.state.slam.phase;
|
|
382
|
+
this.state.slam.phase = "/reflect"; // Use skill ID
|
|
383
|
+
return true; // Reflection triggered
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Complete the daemon run
|
|
391
|
+
*/
|
|
392
|
+
private async complete(): Promise<void> {
|
|
393
|
+
console.log("[GLMDaemon] Task complete!");
|
|
394
|
+
this.state.slam.phase = "/complete";
|
|
395
|
+
this.running = false;
|
|
396
|
+
|
|
397
|
+
// Run session end hook
|
|
398
|
+
await this.hooks.execute("onSessionEnd", this.state);
|
|
399
|
+
|
|
400
|
+
// Save final state
|
|
401
|
+
await this.stateManager.save(this.state);
|
|
402
|
+
|
|
403
|
+
// Create PR if enabled
|
|
404
|
+
if (this.state.git.autoPR) {
|
|
405
|
+
// TODO: Use MCP git tools for PR creation
|
|
406
|
+
console.log("[GLMDaemon] Auto-PR would happen here via MCP git tools");
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Check if workflow is complete
|
|
412
|
+
*/
|
|
413
|
+
private async checkCompletion(): Promise<boolean> {
|
|
414
|
+
const context = this.createSkillContext();
|
|
415
|
+
return isWorkflowComplete(this.workflow, context);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Stop the daemon
|
|
420
|
+
*/
|
|
421
|
+
async stop(): Promise<void> {
|
|
422
|
+
console.log("[GLMDaemon] Stopping...");
|
|
423
|
+
this.running = false;
|
|
424
|
+
|
|
425
|
+
// Cleanup
|
|
426
|
+
if (this.team) {
|
|
427
|
+
await this.team.broadcast("team-lead", "Daemon stopping");
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Save final state
|
|
431
|
+
await this.stateManager.save(this.state);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Get daemon status
|
|
436
|
+
*/
|
|
437
|
+
getStatus(): GLMDaemonStatus {
|
|
438
|
+
return {
|
|
439
|
+
id: this.config.teamName,
|
|
440
|
+
status: this.running ? "running" : "stopped",
|
|
441
|
+
phase: this.state.slam.phase,
|
|
442
|
+
iteration: this.state.iteration,
|
|
443
|
+
prompt: this.state.prompt,
|
|
444
|
+
currentTask: this.state.slam.state.currentTask,
|
|
445
|
+
totalSubtasks: this.state.slam.subtasks.length,
|
|
446
|
+
completedSubtasks: this.state.slam.completedSubtasks.length,
|
|
447
|
+
startedAt: this.state.startTime,
|
|
448
|
+
lastUpdate: this.state.lastUpdate,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Get reflection status and summaries
|
|
454
|
+
*/
|
|
455
|
+
getReflectionStatus(): {
|
|
456
|
+
enabled: boolean;
|
|
457
|
+
toolLimit: number;
|
|
458
|
+
toolCount: number;
|
|
459
|
+
totalToolCount: number;
|
|
460
|
+
checkpointCount: number;
|
|
461
|
+
lastReflection: string | null;
|
|
462
|
+
summaries: import("./types.js").GLMReflectionSummary[];
|
|
463
|
+
} {
|
|
464
|
+
const config = this.config.reflection || {
|
|
465
|
+
enabled: false,
|
|
466
|
+
toolLimit: 0,
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
return {
|
|
470
|
+
enabled: config.enabled,
|
|
471
|
+
toolLimit: config.toolLimit,
|
|
472
|
+
toolCount: this.state.reflection.toolCount,
|
|
473
|
+
totalToolCount: this.state.reflection.totalToolCount,
|
|
474
|
+
checkpointCount: this.state.reflection.checkpointCount,
|
|
475
|
+
lastReflection: this.state.reflection.lastReflection,
|
|
476
|
+
summaries: this.state.reflection.summaries,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Force a reflection checkpoint now
|
|
482
|
+
*/
|
|
483
|
+
async forceReflection(): Promise<import("./types.js").GLMReflectionSummary | null> {
|
|
484
|
+
if (!this.running) {
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Store current phase
|
|
489
|
+
this.state.slam.previousPhase = this.state.slam.phase;
|
|
490
|
+
this.state.slam.phase = "/reflect";
|
|
491
|
+
|
|
492
|
+
// Execute reflection skill
|
|
493
|
+
const context = this.createSkillContext();
|
|
494
|
+
const result = await skillRegistry.execute("/reflect", context);
|
|
495
|
+
|
|
496
|
+
// Return to previous phase
|
|
497
|
+
if (result.stateUpdates?.slam?.phase) {
|
|
498
|
+
this.state.slam.phase = result.stateUpdates.slam.phase;
|
|
499
|
+
} else {
|
|
500
|
+
this.state.slam.phase = this.state.slam.previousPhase || "/execute-subtask";
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Return the summary from result data
|
|
504
|
+
return result.data as any || null;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Get the current workflow config
|
|
509
|
+
*/
|
|
510
|
+
getWorkflow(): SkillWorkflowConfig {
|
|
511
|
+
return this.workflow;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Get workflow configuration
|
|
516
|
+
*/
|
|
517
|
+
getWorkflowConfig() {
|
|
518
|
+
return this.workflow;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Get custom context (for debugging)
|
|
523
|
+
*/
|
|
524
|
+
getContext(): Record<string, unknown> {
|
|
525
|
+
return { ...this.customContext };
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* List available skills
|
|
530
|
+
*/
|
|
531
|
+
listSkills(): Array<{ id: string; name: string; description: string }> {
|
|
532
|
+
return skillRegistry.getInfo();
|
|
533
|
+
}
|
|
534
|
+
}
|
package/src/hooks.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GLM Daemon - Hook System
|
|
3
|
+
*
|
|
4
|
+
* Implements hook lifecycle for daemon events.
|
|
5
|
+
* Based on Claude Code's hook system.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { GLMHooksConfig, GLMDaemonState } from "./types.js";
|
|
9
|
+
|
|
10
|
+
type GLMHookHandler = (
|
|
11
|
+
...args: unknown[]
|
|
12
|
+
) => Promise<unknown> | unknown;
|
|
13
|
+
|
|
14
|
+
export class HookSystem {
|
|
15
|
+
private hooks: Map<string, GLMHookHandler[]> = new Map();
|
|
16
|
+
|
|
17
|
+
constructor(config: GLMHooksConfig) {
|
|
18
|
+
// Register hooks from config
|
|
19
|
+
if (config.onSessionStart) {
|
|
20
|
+
this.register("onSessionStart", config.onSessionStart as GLMHookHandler);
|
|
21
|
+
}
|
|
22
|
+
if (config.onSessionEnd) {
|
|
23
|
+
this.register("onSessionEnd", config.onSessionEnd as GLMHookHandler);
|
|
24
|
+
}
|
|
25
|
+
if (config.onPreToolUse) {
|
|
26
|
+
this.register("onPreToolUse", config.onPreToolUse as GLMHookHandler);
|
|
27
|
+
}
|
|
28
|
+
if (config.onPostToolUse) {
|
|
29
|
+
this.register("onPostToolUse", config.onPostToolUse as GLMHookHandler);
|
|
30
|
+
}
|
|
31
|
+
if (config.onSubagentStart) {
|
|
32
|
+
this.register("onSubagentStart", config.onSubagentStart as GLMHookHandler);
|
|
33
|
+
}
|
|
34
|
+
if (config.onSubagentStop) {
|
|
35
|
+
this.register("onSubagentStop", config.onSubagentStop as GLMHookHandler);
|
|
36
|
+
}
|
|
37
|
+
if (config.onIterationStart) {
|
|
38
|
+
this.register("onIterationStart", config.onIterationStart as GLMHookHandler);
|
|
39
|
+
}
|
|
40
|
+
if (config.onIterationEnd) {
|
|
41
|
+
this.register("onIterationEnd", config.onIterationEnd as GLMHookHandler);
|
|
42
|
+
}
|
|
43
|
+
if (config.onReflection) {
|
|
44
|
+
this.register("onReflection", config.onReflection as GLMHookHandler);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Register a hook handler
|
|
50
|
+
*/
|
|
51
|
+
register(event: string, handler: GLMHookHandler): void {
|
|
52
|
+
if (!this.hooks.has(event)) {
|
|
53
|
+
this.hooks.set(event, []);
|
|
54
|
+
}
|
|
55
|
+
this.hooks.get(event)!.push(handler);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Execute all handlers for an event
|
|
60
|
+
*/
|
|
61
|
+
async execute(event: string, ...args: unknown[]): Promise<void> {
|
|
62
|
+
const handlers = this.hooks.get(event);
|
|
63
|
+
if (!handlers) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (const handler of handlers) {
|
|
68
|
+
try {
|
|
69
|
+
await handler(...args);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error(`[HookSystem] Error in ${event}:`, error);
|
|
72
|
+
// Continue executing other hooks even if one fails
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Remove all handlers for an event
|
|
79
|
+
*/
|
|
80
|
+
clear(event: string): void {
|
|
81
|
+
this.hooks.delete(event);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Remove all hooks
|
|
86
|
+
*/
|
|
87
|
+
clearAll(): void {
|
|
88
|
+
this.hooks.clear();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get registered events
|
|
93
|
+
*/
|
|
94
|
+
getEvents(): string[] {
|
|
95
|
+
return Array.from(this.hooks.keys());
|
|
96
|
+
}
|
|
97
|
+
}
|