@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightcone-ai/daemon",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -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 index for system prompt + MCP derivation (non-blocking on failure)
100
+ // Fetch skills for system prompt + MCP derivation (non-blocking on failure)
78
101
  let skills = [];
79
102
  try {
80
- const chParam = teamId ? `?teamId=${encodeURIComponent(teamId)}` : '';
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') {
@@ -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 chParam = currentTeamId ? `?teamId=${encodeURIComponent(currentTeamId)}` : '';
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 the current team.', {
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 body = { name, description, content, tags: tags ?? [] };
293
- if (currentTeamId) body.teamId = currentTeamId;
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 ─────────────────────────────────────────────────────────────
@@ -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 channel.
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 & Memory
192
+ ## Workspace Structure
194
193
 
195
- Your memory is stored on the server and persists across machines and restarts. Use these MCP tools:
194
+ Your workspace is organized in two layers:
196
195
 
197
- - \`${t("read_memory")}({ path })\` — read a memory file (e.g. \`"MEMORY.md"\`, \`"notes/work-log.md"\`)
198
- - \`${t("write_memory")}({ path, content })\` — save a memory file (full replace)
199
- - \`${t("list_memory")}()\` — list all your memory files
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
- ### MEMORY.md Your Memory Index (CRITICAL)
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
- \`MEMORY.md\` is the **entry point** to all your knowledge. It is the first file read on every startup (including after context compression). Structure it as an index that points to everything you know.
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 definition, evolved over time>
238
+ <your role in this team, evolved over time>
210
239
 
211
240
  ## Key Knowledge
212
- - Read notes/user-preferences.md for user preferences and conventions
213
- - Read notes/teams.md for what each team is about and ongoing work
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
- ### What to memorize
223
-
224
- **Actively observe and record** the following kinds of knowledge as you encounter them in conversations:
225
-
226
- 1. **User preferences** How the user likes things done, communication style, coding conventions, tool preferences, recurring patterns in their requests.
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
- ### Compaction safety (CRITICAL)
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
- Your context will be periodically compressed to stay within limits. When this happens, you lose your in-context conversation history but MEMORY.md is always re-read. Therefore:
261
+ ### Compaction safety
249
262
 
250
- - **MEMORY.md must be self-sufficient as a recovery point.** After reading it, you should be able to understand who you are, what you know, and what you were working on.
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