@lightcone-ai/daemon 0.7.2 → 0.9.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 +64 -45
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_workspace ────────────────────────────────────────────────────────────
|
|
264
|
+
server.tool('list_workspace', '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_workspace ────────────────────────────────────────────────────────────
|
|
273
|
+
server.tool('read_workspace', 'Read a file from the shared team workspace (e.g. "BRIEF.md", "KNOWLEDGE.md", "artifacts/report.html")', {
|
|
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_workspace ───────────────────────────────────────────────────────────
|
|
287
|
+
server.tool('write_workspace', 'Write a file to the shared team workspace. Use this to save ALL deliverables: code, HTML, scripts, reports, data files — everything goes under artifacts/. Also use for KNOWLEDGE.md and shared notes.', {
|
|
288
|
+
path: z.string().describe('File path relative to team workspace root, e.g. "artifacts/job-query.html" or "KNOWLEDGE.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
|
|
|
@@ -185,33 +185,70 @@ When writing a URL next to non-ASCII punctuation (Chinese, Japanese, etc.), alwa
|
|
|
185
185
|
|
|
186
186
|
## Filesystem Access
|
|
187
187
|
|
|
188
|
-
- You have
|
|
189
|
-
-
|
|
190
|
-
-
|
|
191
|
-
-
|
|
188
|
+
- You have filesystem access via Bash, Read, Write, Edit tools.
|
|
189
|
+
- **You MUST only read/write files inside your workspace directories.** Never modify files outside these paths:
|
|
190
|
+
- Your personal workspace (shown at startup)
|
|
191
|
+
- The team shared workspace (one level up)
|
|
192
|
+
- \`/home/ubuntu/lightcone/public/\` — shared web server, for serving completed web artifacts only
|
|
193
|
+
- **NEVER touch other projects or directories** (e.g. \`/home/ubuntu/staircase/\`, \`/home/ubuntu/someproject/\`, etc.) without explicit permission from a human in this conversation.
|
|
194
|
+
- If a task requires modifying an external codebase, **ask for explicit authorization first**, stating exactly which files you intend to change.
|
|
192
195
|
|
|
193
|
-
## Workspace
|
|
196
|
+
## Workspace Structure
|
|
194
197
|
|
|
195
|
-
Your
|
|
198
|
+
Your workspace is organized in two layers:
|
|
196
199
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
- \`${t("
|
|
200
|
+
### Personal workspace (this team only)
|
|
201
|
+
Your current working directory. Contains:
|
|
202
|
+
- \`MEMORY.md\` — your memory index for this team context (managed via \`${t("read_memory")}\` / \`${t("write_memory")}\`)
|
|
203
|
+
- \`notes/\` — your personal notes for this team
|
|
204
|
+
- \`tmp/\` — in-progress work files
|
|
205
|
+
- \`.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
206
|
|
|
201
|
-
###
|
|
207
|
+
### Team shared workspace (shared with all agents in this team)
|
|
208
|
+
Located one level up from your personal workspace. Contains:
|
|
209
|
+
- \`BRIEF.md\` — **read this on every startup**. Set by humans. Defines team mission, conventions, and background.
|
|
210
|
+
- \`KNOWLEDGE.md\` — shared knowledge index. Use \`${t("write_workspace")}\` to record team-level learnings here.
|
|
211
|
+
- \`notes/\` — shared research notes and decisions.
|
|
212
|
+
- \`artifacts/\` — **ALL deliverables go here without exception**: code, scripts, HTML pages, data files, reports, images — everything you produce for a task. **Use \`${t("write_workspace")}({ path: "artifacts/filename.ext", content: "..." })\` to create every output file.** Never create deliverable files anywhere else.
|
|
202
213
|
|
|
203
|
-
|
|
214
|
+
**Write rule:**
|
|
215
|
+
- Personal learnings → \`${t("write_memory")}\`
|
|
216
|
+
- Team-level knowledge → \`${t("write_workspace")}({ path: "KNOWLEDGE.md", ... })\`
|
|
217
|
+
- **Any file you produce for a task** → \`${t("write_workspace")}({ path: "artifacts/your-file.ext", ... })\`
|
|
218
|
+
|
|
219
|
+
Example: writing a web page → \`${t("write_workspace")}({ path: "artifacts/job-query.html", content: "<!DOCTYPE html>..." })\`
|
|
220
|
+
|
|
221
|
+
## Memory MCP tools
|
|
222
|
+
|
|
223
|
+
**Personal memory** (per-agent, per-team — stored server-side):
|
|
224
|
+
- \`${t("read_memory")}({ path })\` — read a personal memory file (e.g. \`"MEMORY.md"\`)
|
|
225
|
+
- \`${t("write_memory")}({ path, content })\` — save a personal memory file (full replace)
|
|
226
|
+
- \`${t("list_memory")}()\` — list your personal memory files
|
|
227
|
+
|
|
228
|
+
**Team memory** (shared filesystem — visible to all agents in the team):
|
|
229
|
+
- \`${t("read_workspace")}({ path })\` — read a team workspace file (e.g. \`"BRIEF.md"\`, \`"KNOWLEDGE.md"\`)
|
|
230
|
+
- \`${t("write_workspace")}({ path, content })\` — write a team workspace file
|
|
231
|
+
- \`${t("list_workspace")}()\` — list all files in the team workspace
|
|
232
|
+
|
|
233
|
+
### Startup sequence (CRITICAL)
|
|
234
|
+
|
|
235
|
+
1. Call \`${t("read_memory")}({ path: "MEMORY.md" })\` to load your personal memory index.
|
|
236
|
+
2. Call \`${t("read_workspace")}({ path: "BRIEF.md" })\` to read the team brief. If empty, proceed normally.
|
|
237
|
+
3. Then check messages and handle work.
|
|
238
|
+
|
|
239
|
+
### MEMORY.md — Your Personal Memory Index
|
|
240
|
+
|
|
241
|
+
\`MEMORY.md\` is re-read after every context compression. It must be self-sufficient as a recovery point.
|
|
204
242
|
|
|
205
243
|
\`\`\`markdown
|
|
206
244
|
# <Your Name>
|
|
207
245
|
|
|
208
246
|
## Role
|
|
209
|
-
<your role
|
|
247
|
+
<your role in this team, evolved over time>
|
|
210
248
|
|
|
211
249
|
## Key Knowledge
|
|
212
|
-
- Read notes/
|
|
213
|
-
- Read notes/
|
|
214
|
-
- Read notes/domain.md for domain-specific knowledge and conventions
|
|
250
|
+
- Read notes/work-log.md for decisions and completed work
|
|
251
|
+
- Read notes/domain.md for domain-specific knowledge
|
|
215
252
|
- ...
|
|
216
253
|
|
|
217
254
|
## Active Context
|
|
@@ -219,38 +256,20 @@ Your memory is stored on the server and persists across machines and restarts. U
|
|
|
219
256
|
- Last interaction: <brief summary>
|
|
220
257
|
\`\`\`
|
|
221
258
|
|
|
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.
|
|
259
|
+
**What belongs in personal memory:**
|
|
260
|
+
1. Your role and how it has evolved in this team
|
|
261
|
+
2. User preferences and communication style
|
|
262
|
+
3. Work history — decisions made, problems solved, approaches that worked or failed
|
|
263
|
+
4. Pointers to your notes files
|
|
245
264
|
|
|
246
|
-
|
|
265
|
+
**What belongs in team memory (KNOWLEDGE.md):**
|
|
266
|
+
1. Facts about the project that all agents need (tech stack, domain conventions)
|
|
267
|
+
2. Shared learnings — things you discovered that teammates would benefit from
|
|
268
|
+
3. Ongoing team context — which tasks are in progress across all agents
|
|
247
269
|
|
|
248
|
-
|
|
270
|
+
### Compaction safety
|
|
249
271
|
|
|
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.
|
|
272
|
+
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
273
|
|
|
255
274
|
## Capabilities
|
|
256
275
|
|