@lightcone-ai/daemon 0.7.2 → 0.8.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/package.json +1 -1
- package/src/agent-manager.js +32 -7
- package/src/chat-bridge.js +37 -7
- package/src/drivers/claude.js +52 -42
package/package.json
CHANGED
package/src/agent-manager.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
|
-
import { mkdirSync, statSync } from 'fs';
|
|
2
|
+
import { mkdirSync, statSync, writeFileSync, readdirSync, unlinkSync } from 'fs';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { buildSystemPrompt } from './drivers/claude.js';
|
|
@@ -40,14 +40,36 @@ export class AgentManager {
|
|
|
40
40
|
|
|
41
41
|
// ── private ───────────────────────────────────────────────────────────────
|
|
42
42
|
|
|
43
|
+
_teamWorkspaceDir(teamId) {
|
|
44
|
+
const dir = path.join(homedir(), '.lightcone', 'workspace', teamId ?? '_global');
|
|
45
|
+
mkdirSync(path.join(dir, 'artifacts'), { recursive: true });
|
|
46
|
+
mkdirSync(path.join(dir, 'notes'), { recursive: true });
|
|
47
|
+
return dir;
|
|
48
|
+
}
|
|
49
|
+
|
|
43
50
|
_workspaceDir(agentId, teamId) {
|
|
44
|
-
const dir = teamId
|
|
45
|
-
? path.join(homedir(), '.lightcone', 'agents', teamId, agentId)
|
|
46
|
-
: path.join(homedir(), '.lightcone', 'agents', agentId);
|
|
51
|
+
const dir = path.join(homedir(), '.lightcone', 'workspace', teamId ?? '_global', agentId);
|
|
47
52
|
mkdirSync(dir, { recursive: true });
|
|
48
53
|
return dir;
|
|
49
54
|
}
|
|
50
55
|
|
|
56
|
+
_materializeSkills(workspaceDir, skills) {
|
|
57
|
+
const skillsDir = path.join(workspaceDir, '.skills');
|
|
58
|
+
mkdirSync(skillsDir, { recursive: true });
|
|
59
|
+
// Remove stale skill files
|
|
60
|
+
try {
|
|
61
|
+
for (const f of readdirSync(skillsDir)) {
|
|
62
|
+
unlinkSync(path.join(skillsDir, f));
|
|
63
|
+
}
|
|
64
|
+
} catch { /* ignore */ }
|
|
65
|
+
// Write each skill with content
|
|
66
|
+
for (const skill of skills) {
|
|
67
|
+
if (!skill.content) continue;
|
|
68
|
+
const filename = skill.name.replace(/[^a-zA-Z0-9_-]/g, '-') + '.md';
|
|
69
|
+
writeFileSync(path.join(skillsDir, filename), `# ${skill.name}\n\n${skill.content}\n`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
51
73
|
_formatDeliveryText(message) {
|
|
52
74
|
return `New message in ${message.team_type === 'dm' ? 'dm from' : `#${message.team_name} from`} ${message.sender_name}: ${message.content}`;
|
|
53
75
|
}
|
|
@@ -70,15 +92,15 @@ export class AgentManager {
|
|
|
70
92
|
this.starting.add(key);
|
|
71
93
|
|
|
72
94
|
const runtime = config.runtime ?? 'claude';
|
|
95
|
+
const teamWorkspaceDir = this._teamWorkspaceDir(teamId);
|
|
73
96
|
const workspaceDir = this._workspaceDir(agentId, teamId);
|
|
74
97
|
const chatBridgePath = new URL('./chat-bridge.js', import.meta.url).pathname;
|
|
75
98
|
const startupMsg = runtime === 'codex' ? this._takePendingMessage(key) : null;
|
|
76
99
|
|
|
77
|
-
// Fetch skills
|
|
100
|
+
// Fetch skills for system prompt + MCP derivation (non-blocking on failure)
|
|
78
101
|
let skills = [];
|
|
79
102
|
try {
|
|
80
|
-
const
|
|
81
|
-
const res = await fetch(`${this.serverUrl}/internal/agent/${agentId}/skills${chParam}`, {
|
|
103
|
+
const res = await fetch(`${this.serverUrl}/internal/agent/${agentId}/skills`, {
|
|
82
104
|
headers: { 'Authorization': `Bearer ${this.machineApiKey}` },
|
|
83
105
|
});
|
|
84
106
|
if (res.ok) skills = await res.json();
|
|
@@ -86,6 +108,9 @@ export class AgentManager {
|
|
|
86
108
|
console.log(`[AgentManager] Skills fetch failed for ${agentId} (non-fatal): ${err.message}`);
|
|
87
109
|
}
|
|
88
110
|
|
|
111
|
+
// Materialize bound skills into .skills/ directory
|
|
112
|
+
this._materializeSkills(workspaceDir, skills);
|
|
113
|
+
|
|
89
114
|
let proc;
|
|
90
115
|
|
|
91
116
|
if (runtime === 'kimi') {
|
package/src/chat-bridge.js
CHANGED
|
@@ -260,10 +260,42 @@ server.tool('write_memory', 'Write or update a memory file (full content replace
|
|
|
260
260
|
return { content: [{ type: 'text', text: `Saved ${path}` }] };
|
|
261
261
|
});
|
|
262
262
|
|
|
263
|
+
// ── list_team_memory ──────────────────────────────────────────────────────────
|
|
264
|
+
server.tool('list_team_memory', 'List all files in the shared team workspace (BRIEF.md, KNOWLEDGE.md, artifacts/, notes/)', {}, async () => {
|
|
265
|
+
if (!currentTeamId) return { content: [{ type: 'text', text: 'No team context.' }] };
|
|
266
|
+
const data = await api('GET', `/team-memory?teamId=${encodeURIComponent(currentTeamId)}`);
|
|
267
|
+
const files = data.files ?? [];
|
|
268
|
+
if (files.length === 0) return { content: [{ type: 'text', text: 'Team workspace is empty.' }] };
|
|
269
|
+
return { content: [{ type: 'text', text: files.map(f => f.path).join('\n') }] };
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// ── read_team_memory ──────────────────────────────────────────────────────────
|
|
273
|
+
server.tool('read_team_memory', 'Read a file from the shared team workspace (e.g. "BRIEF.md", "KNOWLEDGE.md", "notes/decisions.md")', {
|
|
274
|
+
path: z.string().describe('File path relative to team workspace root'),
|
|
275
|
+
}, async ({ path }) => {
|
|
276
|
+
if (!currentTeamId) return { content: [{ type: 'text', text: 'No team context.' }] };
|
|
277
|
+
try {
|
|
278
|
+
const data = await api('GET', `/team-memory?path=${encodeURIComponent(path)}&teamId=${encodeURIComponent(currentTeamId)}`);
|
|
279
|
+
return { content: [{ type: 'text', text: data.content }] };
|
|
280
|
+
} catch (err) {
|
|
281
|
+
if (err.message.includes('404')) return { content: [{ type: 'text', text: `(empty — ${path} has no content yet)` }] };
|
|
282
|
+
throw err;
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// ── write_team_memory ─────────────────────────────────────────────────────────
|
|
287
|
+
server.tool('write_team_memory', 'Write a file to the shared team workspace. Use for team-level knowledge (KNOWLEDGE.md), shared notes, and completed artifacts.', {
|
|
288
|
+
path: z.string().describe('File path relative to team workspace root, e.g. "KNOWLEDGE.md" or "artifacts/report.md"'),
|
|
289
|
+
content: z.string().describe('Full file content to store'),
|
|
290
|
+
}, async ({ path, content }) => {
|
|
291
|
+
if (!currentTeamId) return { content: [{ type: 'text', text: 'No team context.' }] };
|
|
292
|
+
await api('PUT', `/team-memory?path=${encodeURIComponent(path)}&teamId=${encodeURIComponent(currentTeamId)}`, { content });
|
|
293
|
+
return { content: [{ type: 'text', text: `Saved to team workspace: ${path}` }] };
|
|
294
|
+
});
|
|
295
|
+
|
|
263
296
|
// ── skill_list ───────────────────────────────────────────────────────────────
|
|
264
297
|
server.tool('skill_list', 'List all skills available to you (platform + bound). Returns index only (name + description), not full content.', {}, async () => {
|
|
265
|
-
const
|
|
266
|
-
const skills = await api('GET', `/skills${chParam}`);
|
|
298
|
+
const skills = await api('GET', `/skills`);
|
|
267
299
|
if (!skills || skills.length === 0) return { content: [{ type: 'text', text: 'No skills available.' }] };
|
|
268
300
|
const lines = skills.map(s => `- [${s.type}] **${s.name}** — ${s.description}`);
|
|
269
301
|
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
@@ -283,16 +315,14 @@ server.tool('skill_read', 'Read the full content of a skill by name or ID', {
|
|
|
283
315
|
});
|
|
284
316
|
|
|
285
317
|
// ── skill_create ─────────────────────────────────────────────────────────────
|
|
286
|
-
server.tool('skill_create', 'Create a new reusable skill from what you have learned. Auto-binds to
|
|
318
|
+
server.tool('skill_create', 'Create a new reusable skill from what you have learned. Auto-binds to this agent.', {
|
|
287
319
|
name: z.string().describe('Short skill name (lowercase, hyphens ok), e.g. "xhs-posting"'),
|
|
288
320
|
description: z.string().describe('One-line description of what this skill covers'),
|
|
289
321
|
content: z.string().describe('Full skill content in markdown — procedures, steps, tips'),
|
|
290
322
|
tags: z.array(z.string()).optional().describe('Optional tags for categorization'),
|
|
291
323
|
}, async ({ name, description, content, tags }) => {
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
const result = await api('POST', '/skills', body);
|
|
295
|
-
return { content: [{ type: 'text', text: `Skill "${result.name}" created and bound to current team.` }] };
|
|
324
|
+
const result = await api('POST', '/skills', { name, description, content, tags: tags ?? [] });
|
|
325
|
+
return { content: [{ type: 'text', text: `Skill "${result.name}" created and bound to this agent.` }] };
|
|
296
326
|
});
|
|
297
327
|
|
|
298
328
|
// ── skill_update ─────────────────────────────────────────────────────────────
|
package/src/drivers/claude.js
CHANGED
|
@@ -26,7 +26,7 @@ You have MCP tools from the "chat" server. Use ONLY these for communication:
|
|
|
26
26
|
12. **${t("view_file")}** — Download an attached image by its attachment ID so you can view it. Use when messages contain image attachments.
|
|
27
27
|
|
|
28
28
|
CRITICAL RULES:
|
|
29
|
-
- Always communicate through ${t("send_message")}. This is your only output
|
|
29
|
+
- Always communicate through ${t("send_message")}. This is your only output method.
|
|
30
30
|
- Use only the provided MCP tools for messaging — they are already available and ready.
|
|
31
31
|
- Always claim a task via ${t("claim_tasks")} before starting work on it. If the claim fails, move on to a different task.
|
|
32
32
|
|
|
@@ -188,30 +188,58 @@ When writing a URL next to non-ASCII punctuation (Chinese, Japanese, etc.), alwa
|
|
|
188
188
|
- You have full filesystem access via Bash, Read, Write, Edit tools.
|
|
189
189
|
- The shared web server public directory is: \`/home/ubuntu/lightcone/public/\`
|
|
190
190
|
- Write web app files directly there so they are served immediately.
|
|
191
|
-
- Your current working directory is a temporary workspace for code, scripts, and build artifacts.
|
|
192
191
|
|
|
193
|
-
## Workspace
|
|
192
|
+
## Workspace Structure
|
|
194
193
|
|
|
195
|
-
Your
|
|
194
|
+
Your workspace is organized in two layers:
|
|
196
195
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
- \`${t("
|
|
196
|
+
### Personal workspace (this team only)
|
|
197
|
+
Your current working directory. Contains:
|
|
198
|
+
- \`MEMORY.md\` — your memory index for this team context (managed via \`${t("read_memory")}\` / \`${t("write_memory")}\`)
|
|
199
|
+
- \`notes/\` — your personal notes for this team
|
|
200
|
+
- \`tmp/\` — in-progress work files
|
|
201
|
+
- \`.skills/\` — **read-only**, system-injected skill files. Each \`.md\` file is a skill bound to you. Read them with your Read tool — they are already on disk.
|
|
200
202
|
|
|
201
|
-
###
|
|
203
|
+
### Team shared workspace (shared with all agents in this team)
|
|
204
|
+
Located one level up from your personal workspace. Contains:
|
|
205
|
+
- \`BRIEF.md\` — **read this on every startup**. Set by humans. Defines team mission, conventions, and background.
|
|
206
|
+
- \`KNOWLEDGE.md\` — shared knowledge index. Use \`${t("write_team_memory")}\` to record team-level learnings here.
|
|
207
|
+
- \`notes/\` — shared research notes and decisions.
|
|
208
|
+
- \`artifacts/\` — completed team outputs (images, documents, reports). Move finished work here.
|
|
202
209
|
|
|
203
|
-
|
|
210
|
+
**Write rule:** personal learnings → \`${t("write_memory")}\`. Team-level knowledge → \`${t("write_team_memory")}\`. Finished files → \`artifacts/\` via \`${t("write_team_memory")}\`.
|
|
211
|
+
|
|
212
|
+
## Memory MCP tools
|
|
213
|
+
|
|
214
|
+
**Personal memory** (per-agent, per-team — stored server-side):
|
|
215
|
+
- \`${t("read_memory")}({ path })\` — read a personal memory file (e.g. \`"MEMORY.md"\`)
|
|
216
|
+
- \`${t("write_memory")}({ path, content })\` — save a personal memory file (full replace)
|
|
217
|
+
- \`${t("list_memory")}()\` — list your personal memory files
|
|
218
|
+
|
|
219
|
+
**Team memory** (shared filesystem — visible to all agents in the team):
|
|
220
|
+
- \`${t("read_team_memory")}({ path })\` — read a team workspace file (e.g. \`"BRIEF.md"\`, \`"KNOWLEDGE.md"\`)
|
|
221
|
+
- \`${t("write_team_memory")}({ path, content })\` — write a team workspace file
|
|
222
|
+
- \`${t("list_team_memory")}()\` — list all files in the team workspace
|
|
223
|
+
|
|
224
|
+
### Startup sequence (CRITICAL)
|
|
225
|
+
|
|
226
|
+
1. Call \`${t("read_memory")}({ path: "MEMORY.md" })\` to load your personal memory index.
|
|
227
|
+
2. Call \`${t("read_team_memory")}({ path: "BRIEF.md" })\` to read the team brief. If empty, proceed normally.
|
|
228
|
+
3. Then check messages and handle work.
|
|
229
|
+
|
|
230
|
+
### MEMORY.md — Your Personal Memory Index
|
|
231
|
+
|
|
232
|
+
\`MEMORY.md\` is re-read after every context compression. It must be self-sufficient as a recovery point.
|
|
204
233
|
|
|
205
234
|
\`\`\`markdown
|
|
206
235
|
# <Your Name>
|
|
207
236
|
|
|
208
237
|
## Role
|
|
209
|
-
<your role
|
|
238
|
+
<your role in this team, evolved over time>
|
|
210
239
|
|
|
211
240
|
## Key Knowledge
|
|
212
|
-
- Read notes/
|
|
213
|
-
- Read notes/
|
|
214
|
-
- Read notes/domain.md for domain-specific knowledge and conventions
|
|
241
|
+
- Read notes/work-log.md for decisions and completed work
|
|
242
|
+
- Read notes/domain.md for domain-specific knowledge
|
|
215
243
|
- ...
|
|
216
244
|
|
|
217
245
|
## Active Context
|
|
@@ -219,38 +247,20 @@ Your memory is stored on the server and persists across machines and restarts. U
|
|
|
219
247
|
- Last interaction: <brief summary>
|
|
220
248
|
\`\`\`
|
|
221
249
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
2. **World/project context** — The project structure, tech stack, architectural decisions, team conventions, deployment patterns.
|
|
228
|
-
3. **Domain knowledge** — Domain-specific terminology, conventions, best practices you learn through tasks.
|
|
229
|
-
4. **Work history** — What has been done, decisions made and why, problems solved, approaches that worked or failed.
|
|
230
|
-
5. **Team context** — What each team is about, who participates, what's being discussed, ongoing tasks per team.
|
|
231
|
-
6. **Other agents** — What other agents do, their specialties, collaboration patterns, how to work with them effectively.
|
|
232
|
-
|
|
233
|
-
### How to organize memory
|
|
234
|
-
|
|
235
|
-
- **MEMORY.md** is always the index. Keep it concise but comprehensive as a table of contents.
|
|
236
|
-
- Create a \`notes/\` directory for detailed knowledge files. Use descriptive names:
|
|
237
|
-
- \`notes/user-preferences.md\` — User's preferences and conventions
|
|
238
|
-
- \`notes/teams.md\` — Summary of each team and its purpose
|
|
239
|
-
- \`notes/work-log.md\` — Important decisions and completed work
|
|
240
|
-
- \`notes/<domain>.md\` — Domain-specific knowledge
|
|
241
|
-
- You can also create any other files or directories for your work (scripts, notes, data, etc.)
|
|
242
|
-
- **Update notes proactively** — Don't wait to be asked. When you learn something important, write it down.
|
|
243
|
-
- **Keep MEMORY.md current** — After updating notes, update the index in MEMORY.md if new files were added.
|
|
244
|
-
- Do NOT store memory as local files — always use \`${t("write_memory")}\` so it persists server-side.
|
|
250
|
+
**What belongs in personal memory:**
|
|
251
|
+
1. Your role and how it has evolved in this team
|
|
252
|
+
2. User preferences and communication style
|
|
253
|
+
3. Work history — decisions made, problems solved, approaches that worked or failed
|
|
254
|
+
4. Pointers to your notes files
|
|
245
255
|
|
|
246
|
-
|
|
256
|
+
**What belongs in team memory (KNOWLEDGE.md):**
|
|
257
|
+
1. Facts about the project that all agents need (tech stack, domain conventions)
|
|
258
|
+
2. Shared learnings — things you discovered that teammates would benefit from
|
|
259
|
+
3. Ongoing team context — which tasks are in progress across all agents
|
|
247
260
|
|
|
248
|
-
|
|
261
|
+
### Compaction safety
|
|
249
262
|
|
|
250
|
-
|
|
251
|
-
- **Before a long task**, write a brief "Active Context" note in MEMORY.md so you can resume if interrupted mid-task.
|
|
252
|
-
- **After completing work**, update your notes and MEMORY.md index so nothing is lost.
|
|
253
|
-
- Keep MEMORY.md complete enough that context compression preserves: which team is about what, what tasks are in progress, what the user has asked for, and what other agents are doing.
|
|
263
|
+
Before a long task, write a brief "Active Context" note in MEMORY.md. After completing work, update notes and MEMORY.md so nothing is lost after compression.
|
|
254
264
|
|
|
255
265
|
## Capabilities
|
|
256
266
|
|