@yeaft/webchat-agent 1.0.356 → 1.0.358

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/cli.js CHANGED
@@ -64,13 +64,7 @@ if (command === 'doctor') {
64
64
  } else if (command === 'llm') {
65
65
  await handleLlmCommand(subArgs);
66
66
  } else if (command === 'local') {
67
- try {
68
- const { runLocal } = await import('./local-run.js');
69
- await runLocal(subArgs);
70
- } catch (error) {
71
- console.error(`Local run failed: ${error.message}`);
72
- process.exit(1);
73
- }
67
+ await handleLocalCommand(subArgs);
74
68
  } else if (command === 'upgrade') {
75
69
  await upgrade(subArgs);
76
70
  } else if (command === '--version' || command === '-v') {
@@ -84,6 +78,37 @@ if (command === 'doctor') {
84
78
  parseAndStart(args);
85
79
  }
86
80
 
81
+ /** Dispatch the local foreground and managed-service command family. */
82
+ export async function handleLocalCommand(subArgs, options = {}) {
83
+ const printHelpFn = options.printHelp || printHelp;
84
+ const warn = options.warn || console.warn;
85
+ const loadLocalService = options.loadLocalService || (() => import('./local-service.js'));
86
+ const loadLocalRun = options.loadLocalRun || (() => import('./local-run.js'));
87
+ try {
88
+ const localCommand = subArgs[0];
89
+ if (localCommand === '--help' || localCommand === '-h') {
90
+ printHelpFn();
91
+ return;
92
+ }
93
+ warnDeprecatedInstanceArg(subArgs, warn);
94
+ if (SERVICE_COMMANDS.includes(localCommand)) {
95
+ const { handleLocalServiceCommand } = await loadLocalService();
96
+ await handleLocalServiceCommand(localCommand, subArgs.slice(1));
97
+ return;
98
+ }
99
+ const { runLocal } = await loadLocalRun();
100
+ await runLocal(subArgs);
101
+ } catch (error) {
102
+ const message = `Local run failed: ${error.message}`;
103
+ if (options.onError) {
104
+ options.onError(message, error);
105
+ return;
106
+ }
107
+ console.error(message);
108
+ process.exit(1);
109
+ }
110
+ }
111
+
87
112
  function printHelp() {
88
113
  console.log(`
89
114
  ${pkg.name} v${pkg.version}
@@ -91,6 +116,11 @@ function printHelp() {
91
116
  Usage:
92
117
  yeaft-agent [options] Run agent in foreground
93
118
  yeaft-agent local [options] Run local Web UI, server, and agent
119
+ yeaft-agent local --background Run local mode in the background
120
+ yeaft-agent local install [options] Install local mode as a managed service
121
+ yeaft-agent local uninstall [options] Remove the local managed service
122
+ yeaft-agent local start|stop|restart|status|logs [options]
123
+ Control the local managed service
94
124
  yeaft-agent install [options] Install as system service
95
125
  yeaft-agent uninstall [options] Remove system service
96
126
  yeaft-agent start [options] Start installed service
@@ -108,6 +138,7 @@ function printHelp() {
108
138
  --server <url> WebSocket server URL (default: ws://localhost:3456)
109
139
  --name <name> Agent name and instance id (default: computer name; invalid chars become -)
110
140
  --port <port> Local server port (local command only; default: 6868)
141
+ --background, -d Detach local mode after spawning it (local command only)
111
142
  --secret <secret> Agent secret for authentication
112
143
  --work-dir <dir> Default working directory (default: cwd)
113
144
  --yeaft-dir <dir> Yeaft data directory for this instance
@@ -124,6 +155,8 @@ function printHelp() {
124
155
  Examples:
125
156
  yeaft-agent local
126
157
  yeaft-agent local --name my-worker --port 7000
158
+ yeaft-agent local --name my-worker --background
159
+ yeaft-agent local install --name my-worker --port 7000
127
160
  yeaft-agent --server wss://your-server.com --name my-worker --secret xxx
128
161
  yeaft-agent install --server wss://your-server.com --name my-worker --secret xxx
129
162
  yeaft-agent install --server wss://your-server.com --name my-worker-2 --secret xxx
package/local-run.js CHANGED
@@ -5,18 +5,23 @@ import { createServer } from 'net';
5
5
  import { dirname, join } from 'path';
6
6
  import { fileURLToPath } from 'url';
7
7
  import { WebSocket } from 'ws';
8
- import { resolveDisplayName, validateInstanceId } from './service/config.js';
8
+ import { resolveServiceInstanceId, resolveYeaftDir } from './service/config.js';
9
9
 
10
10
  const DEFAULT_PORT = 6868;
11
11
  const LOCAL_HOST = '127.0.0.1';
12
12
 
13
13
  export function parseLocalArgs(args, env = process.env) {
14
- const options = { name: resolveDisplayName(args, env), port: DEFAULT_PORT };
14
+ const options = {
15
+ name: resolveServiceInstanceId(args, env),
16
+ port: DEFAULT_PORT,
17
+ background: false,
18
+ yeaftDir: null,
19
+ };
15
20
  for (let i = 0; i < args.length; i++) {
16
21
  const arg = args[i];
17
22
  const value = args[i + 1];
18
- if (arg === '--name') {
19
- if (!value || value.startsWith('-')) throw new Error('--name requires a value');
23
+ if (arg === '--name' || arg === '--instance' || arg === '--yeaft-dir') {
24
+ if (!value || value.startsWith('-')) throw new Error(`${arg} requires a value`);
20
25
  i++;
21
26
  } else if (arg === '--port') {
22
27
  if (!value || value.startsWith('-')) throw new Error('--port requires a value');
@@ -24,11 +29,13 @@ export function parseLocalArgs(args, env = process.env) {
24
29
  options.port = Number(value);
25
30
  if (options.port < 1 || options.port > 65535) throw new Error(`Invalid port: ${value}`);
26
31
  i++;
32
+ } else if (arg === '--background' || arg === '-d') {
33
+ options.background = true;
27
34
  } else {
28
35
  throw new Error(`Unknown local option: ${arg}`);
29
36
  }
30
37
  }
31
- validateInstanceId(options.name);
38
+ options.yeaftDir = resolveYeaftDir(args, env, options.name);
32
39
  return options;
33
40
  }
34
41
 
@@ -127,8 +134,12 @@ function readAgentList(wsUrl) {
127
134
 
128
135
  export async function runLocal(args, options = {}) {
129
136
  const config = parseLocalArgs(args);
137
+ if (config.background && options.backgroundHandled !== true) {
138
+ return launchLocalInBackground(args, options);
139
+ }
130
140
  const paths = options.paths || runtimePaths();
131
141
  const dataDir = options.dataDir || join(homedir(), '.yeaft', 'server');
142
+ const yeaftDir = options.yeaftDir || config.yeaftDir;
132
143
  const url = `http://${LOCAL_HOST}:${config.port}`;
133
144
  const children = new Set();
134
145
  const signalHandlers = new Map();
@@ -173,6 +184,7 @@ export async function runLocal(args, options = {}) {
173
184
  SERVER_DATA_DIR: dataDir,
174
185
  PERF_TRACE_DIR: join(dataDir, 'perf-traces'),
175
186
  SKIP_AUTH: 'true',
187
+ YEAFT_LOCAL_RUN: 'true',
176
188
  WEB_DIR: paths.webDir,
177
189
  },
178
190
  });
@@ -189,6 +201,7 @@ export async function runLocal(args, options = {}) {
189
201
  AGENT_NAME: config.name,
190
202
  YEAFT_AGENT_INSTANCE: config.name,
191
203
  AGENT_SECRET: '',
204
+ YEAFT_DIR: yeaftDir,
192
205
  YEAFT_LOCAL_RUN: 'true',
193
206
  YEAFT_SKIP_STARTUP_INSTALLS: 'true',
194
207
  },
@@ -204,7 +217,7 @@ export async function runLocal(args, options = {}) {
204
217
  server.once('exit', fail('Local server'));
205
218
  agent.once('exit', fail('Local agent'));
206
219
 
207
- await waitForAgent(`ws://${LOCAL_HOST}:${config.port}`, config.name, agent);
220
+ await (options.waitForAgent || waitForAgent)(`ws://${LOCAL_HOST}:${config.port}`, config.name, agent);
208
221
 
209
222
  console.log(`Yeaft local is available at ${url}`);
210
223
  return { url, server, agent, stop };
@@ -213,3 +226,29 @@ export async function runLocal(args, options = {}) {
213
226
  throw error;
214
227
  }
215
228
  }
229
+
230
+ function localDaemonArgs(args) {
231
+ return args.filter(arg => arg !== '--background' && arg !== '-d');
232
+ }
233
+
234
+ export async function launchLocalInBackground(args, options = {}) {
235
+ const config = parseLocalArgs(args);
236
+ const spawnProcess = options.spawn || spawn;
237
+ const cliPath = options.cliPath || join(dirname(fileURLToPath(import.meta.url)), 'cli.js');
238
+ const child = spawnProcess(process.execPath, [cliPath, 'local', ...localDaemonArgs(args)], {
239
+ detached: true,
240
+ stdio: 'ignore',
241
+ windowsHide: true,
242
+ env: {
243
+ ...process.env,
244
+ YEAFT_LOCAL_RUN_BACKGROUND: 'true',
245
+ },
246
+ });
247
+ child.unref();
248
+ const url = `http://${LOCAL_HOST}:${config.port}`;
249
+ const result = { url, pid: child.pid, background: true };
250
+ if (options.quiet !== true) {
251
+ console.log(`Yeaft local is starting in the background at ${url} (PID ${child.pid}).`);
252
+ }
253
+ return result;
254
+ }
@@ -674,7 +674,14 @@ export async function handleAgentOutput(agentId, agent, msg) {
674
674
  }
675
675
  }
676
676
  }
677
- if (catalogChanged) await broadcastSessionCatalog(agent.ownerId);
677
+ // Nested `session_list_updated` events carry the same authoritative
678
+ // snapshot as the top-level alias below. Re-project the server-owned
679
+ // catalog after reconciliation so the unified sidebar updates in both
680
+ // local no-auth and deployed runtimes. Roster changes are handled by
681
+ // their own metadata branch above.
682
+ if ((event?.type === 'session_list_updated' || catalogChanged) && agent.ownerId) {
683
+ await broadcastSessionCatalog(agent.ownerId);
684
+ }
678
685
  break;
679
686
  }
680
687
 
@@ -4,6 +4,7 @@ import { CONFIG } from './config.js';
4
4
  import { verifyAgent } from './auth.js';
5
5
  import { encodeKey } from './encryption.js';
6
6
  import { agents, pendingAgentConnections } from './context.js';
7
+ import { userDb } from './database.js';
7
8
  import {
8
9
  parseMessage, broadcastAgentList, clearAgentDirCache
9
10
  } from './ws-utils.js';
@@ -114,18 +115,37 @@ export function handleAgentConnection(ws, url) {
114
115
 
115
116
  const capabilities = Array.isArray(msg.capabilities) ? msg.capabilities : urlCapabilities;
116
117
  const agentVersion = msg.version || null;
118
+ // Local no-auth mode still has one durable browser owner. This makes
119
+ // the server-side Session catalog persistent without changing generic
120
+ // development-server behavior, which remains ownerless.
121
+ const localOwner = skipAgentAuth && process.env.YEAFT_LOCAL_RUN === 'true'
122
+ ? userDb.getOrCreate('dev-user')
123
+ : null;
124
+ const ownerId = localOwner?.id || authResult.userId;
125
+ const ownerUsername = localOwner?.username || authResult.username;
117
126
  // Authenticated Agents use an owner-scoped key. SKIP_AUTH preserves
118
127
  // its historical unscoped id while still receiving version metadata.
119
128
  resolvedAgentId = skipAgentAuth
120
129
  ? clientAgentId
121
- : buildAgentMapKey(authResult.userId, pending.instanceId || pending.agentId || pending.agentName);
130
+ : buildAgentMapKey(ownerId, pending.instanceId || pending.agentId || pending.agentName);
122
131
  if (!claimAgentConnection(resolvedAgentId, connectionGeneration)) {
123
132
  resolvedAgentId = null;
124
133
  pruneAgentConnectionGenerations();
125
134
  ws.close(1008, 'Superseded by a newer Agent connection');
126
135
  return;
127
136
  }
128
- completeAgentRegistration(ws, resolvedAgentId, pending.agentName, pending.workDir, authResult.sessionKey, capabilities, authResult.userId, authResult.username, agentVersion, pending.instanceId || pending.agentId || pending.agentName);
137
+ completeAgentRegistration(
138
+ ws,
139
+ resolvedAgentId,
140
+ pending.agentName,
141
+ pending.workDir,
142
+ authResult.sessionKey,
143
+ capabilities,
144
+ ownerId,
145
+ ownerUsername,
146
+ agentVersion,
147
+ pending.instanceId || pending.agentId || pending.agentName,
148
+ );
129
149
  pruneAgentConnectionGenerations();
130
150
  }
131
151
  } catch (e) {
@@ -1 +1 @@
1
- {"version":"1.0.356"}
1
+ {"version":"1.0.358"}