@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightcone-ai/daemon",
3
- "version": "0.7.2",
3
+ "version": "0.9.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_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 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
 
@@ -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 full filesystem access via Bash, Read, Write, Edit tools.
189
- - The shared web server public directory is: \`/home/ubuntu/lightcone/public/\`
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.
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 & Memory
196
+ ## Workspace Structure
194
197
 
195
- Your memory is stored on the server and persists across machines and restarts. Use these MCP tools:
198
+ Your workspace is organized in two layers:
196
199
 
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
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
- ### MEMORY.md Your Memory Index (CRITICAL)
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
- \`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.
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 definition, evolved over time>
247
+ <your role in this team, evolved over time>
210
248
 
211
249
  ## 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
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
- ### 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.
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
- ### Compaction safety (CRITICAL)
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
- 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:
270
+ ### Compaction safety
249
271
 
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.
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