@lightcone-ai/daemon 0.23.7 → 0.23.9

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.23.7",
3
+ "version": "0.23.9",
4
4
  "type": "module",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from 'node:child_process';
3
- import { createWriteStream, existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
3
+ import { closeSync, existsSync, openSync, readFileSync, readSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import os from 'node:os';
6
6
  import path from 'node:path';
@@ -20,9 +20,9 @@ function parseArgs(raw) {
20
20
  const opts = { _: [] };
21
21
  for (let i = 0; i < raw.length; i += 1) {
22
22
  const arg = raw[i];
23
- if (arg.startsWith('--')) {
23
+ if (arg.startsWith('-') && arg !== '-') {
24
24
  const next = raw[i + 1];
25
- if (next && !next.startsWith('--')) {
25
+ if (next !== undefined && !next.startsWith('-')) {
26
26
  opts[arg] = next;
27
27
  i += 1;
28
28
  } else {
@@ -45,7 +45,7 @@ function printUsage() {
45
45
  lightcone daemon status
46
46
  lightcone status
47
47
  lightcone doctor [--json]
48
- lightcone logs [--lines 100]
48
+ lightcone logs [--lines 100] [-f|--follow]
49
49
 
50
50
  Notes:
51
51
  Use --code for normal onboarding, or --api-key for advanced/server installs.`);
@@ -121,16 +121,24 @@ function startDaemon({ foreground = false } = {}) {
121
121
  }
122
122
 
123
123
  const logPath = resolveDaemonLogPath();
124
- const out = createWriteStream(logPath, { flags: 'a' });
125
- const child = spawn(process.execPath, args, {
126
- detached: true,
127
- stdio: ['ignore', out, out],
128
- env: {
129
- ...process.env,
130
- SERVER_URL: config.serverUrl,
131
- MACHINE_API_KEY: config.machineApiKey,
132
- },
133
- });
124
+ // Open the log file synchronously and pass its numeric fd: a lazily-opened
125
+ // WriteStream still has fd=null when spawn() runs, which Node rejects as
126
+ // invalid stdio. The child inherits a dup, so the parent closes its copy.
127
+ const logFd = openSync(logPath, 'a');
128
+ let child;
129
+ try {
130
+ child = spawn(process.execPath, args, {
131
+ detached: true,
132
+ stdio: ['ignore', logFd, logFd],
133
+ env: {
134
+ ...process.env,
135
+ SERVER_URL: config.serverUrl,
136
+ MACHINE_API_KEY: config.machineApiKey,
137
+ },
138
+ });
139
+ } finally {
140
+ closeSync(logFd);
141
+ }
134
142
  child.unref();
135
143
  writeFileSync(resolveDaemonPidPath(), `${child.pid}\n`, 'utf8');
136
144
  console.log(`Daemon started pid=${child.pid}`);
@@ -165,6 +173,35 @@ function tailLogs(lines = 100) {
165
173
  console.log(raw.split(/\r?\n/).slice(-(Number.isFinite(count) && count > 0 ? count : 100)).join('\n'));
166
174
  }
167
175
 
176
+ function followLogs(lines = 100) {
177
+ const logPath = resolveDaemonLogPath();
178
+ if (existsSync(logPath)) {
179
+ tailLogs(lines);
180
+ } else {
181
+ console.log(`Waiting for ${logPath} to appear ... (Ctrl+C to stop)`);
182
+ }
183
+ let offset = existsSync(logPath) ? statSync(logPath).size : 0;
184
+ setInterval(() => {
185
+ try {
186
+ if (!existsSync(logPath)) return;
187
+ const size = statSync(logPath).size;
188
+ if (size < offset) offset = 0; // truncated or rotated
189
+ if (size <= offset) return;
190
+ const fd = openSync(logPath, 'r');
191
+ try {
192
+ const buf = Buffer.alloc(size - offset);
193
+ readSync(fd, buf, 0, buf.length, offset);
194
+ process.stdout.write(buf.toString('utf8'));
195
+ } finally {
196
+ closeSync(fd);
197
+ }
198
+ offset = size;
199
+ } catch {
200
+ // transient fs errors during rotation — retry on next tick
201
+ }
202
+ }, 500);
203
+ }
204
+
168
205
  async function main() {
169
206
  const opts = parseArgs(process.argv.slice(2));
170
207
  const [command, subcommand] = opts._;
@@ -226,7 +263,8 @@ async function main() {
226
263
  }
227
264
 
228
265
  if (command === 'logs') {
229
- tailLogs(opts['--lines']);
266
+ if (opts['-f'] || opts['--follow']) followLogs(opts['--lines']);
267
+ else tailLogs(opts['--lines']);
230
268
  return;
231
269
  }
232
270
 
@@ -267,8 +267,11 @@ export function workspacePathFromMediaPath(filePath, workspaceId) {
267
267
  const virtualMatch = normalized.match(/^\/agent-workspace\/([^/]+)\/workspace\/(.+)$/);
268
268
  if (virtualMatch) return { workspaceId: virtualMatch[1], relPath: virtualMatch[2] };
269
269
 
270
+ // The workspace UUID may be followed by an optional per-agent dir segment
271
+ // — the daemon lays artifacts out under /workspace/<wsId>/<agentId>/artifacts/…
272
+ // — so strip it and keep the shared workspace relPath for materialization.
270
273
  const hostMatch = normalized.match(
271
- /\/workspace\/([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})\/((?:artifacts|notes|tmp)\/.+)$/i
274
+ /\/workspace\/([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})\/(?:[0-9a-f-]{36}\/)?((?:artifacts|notes|tmp)\/.+)$/i
272
275
  );
273
276
  if (hostMatch) {
274
277
  return { workspaceId: hostMatch[1], relPath: hostMatch[2] };