@ours.network/fleet 1.2.0-nightly.29 → 1.2.0-nightly.30

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.
@@ -1,9 +1,9 @@
1
1
  {
2
- "version": "1.2.0-nightly.29",
3
- "buildId": "cc1740d5df7b",
4
- "commit": "e4737a56d25b0244222bd94b25c299825b8f916f",
2
+ "version": "1.2.0-nightly.30",
3
+ "buildId": "a2babf441212",
4
+ "commit": "3cd486a81e62d5bd2c345c0d73cb443aee02dee1",
5
5
  "dirty": true,
6
- "builtAt": "2026-10-01T10:23:02.435Z",
6
+ "builtAt": "2026-10-01T21:40:11.246Z",
7
7
  "capabilities": [
8
8
  "workspace.enroll.preserve-profile-v1",
9
9
  "cowork.http-management-v1",
package/dist/cli.js CHANGED
@@ -1544,7 +1544,7 @@ cOpt(program.command('workspace-enroll').description('enroll this host using a p
1544
1544
  const controlDeadline = Date.now() + 20000;
1545
1545
  while (!existsSync(webControlPath())) {
1546
1546
  if (Date.now() >= controlDeadline)
1547
- throw Error('Workspace console is still starting; run ours-fleet link-device when ready');
1547
+ throw Error('Fleet backend did not become ready within 20 seconds. Run ours-fleet web status; on Linux inspect journalctl --user -u ours-fleet-web.service -n 25 --no-pager. Resolve the startup error before running ours-fleet link-device');
1548
1548
  await new Promise(resolve => setTimeout(resolve, 200));
1549
1549
  }
1550
1550
  const link = await requestWebControl('link-device');
@@ -1,6 +1,7 @@
1
1
  import { type Exec } from '../exec.js';
2
2
  export declare const WEB_SYSTEMD_UNIT = "ours-fleet-web.service";
3
3
  export declare const WEB_LAUNCHD_LABEL = "network.ours.fleet.web";
4
+ type ServiceEnvironment = Partial<Record<'OURS_CONFIG' | 'OURS_FLEET_HOME' | 'PATH', string>>;
4
5
  interface ServiceMetadata {
5
6
  version: 3;
6
7
  platform: 'linux' | 'darwin';
@@ -10,6 +11,7 @@ interface ServiceMetadata {
10
11
  configuration?: string;
11
12
  bind?: string;
12
13
  publicOrigin?: string;
14
+ environment?: ServiceEnvironment;
13
15
  }
14
16
  export interface WebServiceOptions {
15
17
  platform?: NodeJS.Platform;
@@ -18,6 +20,7 @@ export interface WebServiceOptions {
18
20
  stateDir?: string;
19
21
  uid?: number;
20
22
  runtimeExecutable?: string;
23
+ environment?: NodeJS.ProcessEnv;
21
24
  }
22
25
  export declare class WebServiceManager {
23
26
  private readonly platform;
@@ -26,6 +29,7 @@ export declare class WebServiceManager {
26
29
  private readonly stateDir;
27
30
  private readonly uid;
28
31
  private readonly runtimeExecutable;
32
+ private readonly environment;
29
33
  constructor(options?: WebServiceOptions);
30
34
  get metadataPath(): string;
31
35
  get definitionPath(): string;
@@ -45,9 +49,9 @@ export declare class WebServiceManager {
45
49
  export declare function systemdUnit(runtime: string, script: string, port: number, configuration?: string, web?: {
46
50
  bind?: string;
47
51
  publicOrigin?: string;
48
- }): string;
52
+ }, environment?: ServiceEnvironment): string;
49
53
  export declare function launchdPlist(runtime: string, script: string, port: number, configuration?: string, web?: {
50
54
  bind?: string;
51
55
  publicOrigin?: string;
52
- }): string;
56
+ }, environment?: ServiceEnvironment): string;
53
57
  export {};
@@ -15,6 +15,7 @@ export class WebServiceManager {
15
15
  stateDir;
16
16
  uid;
17
17
  runtimeExecutable;
18
+ environment;
18
19
  constructor(options = {}) {
19
20
  const platform = options.platform ?? process.platform;
20
21
  if (platform !== 'linux' && platform !== 'darwin')
@@ -25,6 +26,7 @@ export class WebServiceManager {
25
26
  this.stateDir = options.stateDir ?? join(stateRoot(), 'web');
26
27
  this.uid = options.uid ?? process.getuid?.() ?? 501;
27
28
  this.runtimeExecutable = options.runtimeExecutable ?? process.execPath;
29
+ this.environment = serviceEnvironment(options.environment ?? process.env);
28
30
  }
29
31
  get metadataPath() { return join(this.stateDir, 'service.json'); }
30
32
  get definitionPath() {
@@ -42,13 +44,14 @@ export class WebServiceManager {
42
44
  const config = configuration ? resolve(configuration) : undefined;
43
45
  const metadata = {
44
46
  version: VERSION, platform: this.platform, runtime, script: resolvedScript, port,
47
+ environment: this.environment,
45
48
  ...(config ? { configuration: config } : {}),
46
49
  ...(web.bind ? { bind: web.bind } : {}),
47
50
  ...(web.publicOrigin ? { publicOrigin: web.publicOrigin } : {}),
48
51
  };
49
52
  replaceFileAtomically(this.definitionPath, this.platform === 'linux'
50
- ? systemdUnit(runtime, resolvedScript, port, config, web)
51
- : launchdPlist(runtime, resolvedScript, port, config, web), 0o600);
53
+ ? systemdUnit(runtime, resolvedScript, port, config, web, this.environment)
54
+ : launchdPlist(runtime, resolvedScript, port, config, web, this.environment), 0o600);
52
55
  replaceFileAtomically(this.metadataPath, JSON.stringify(metadata, null, 2) + '\n', 0o600);
53
56
  if (this.platform === 'linux') {
54
57
  await this.must('systemctl', ['--user', 'daemon-reload']);
@@ -159,19 +162,20 @@ function validatePort(port) {
159
162
  function systemdQuote(value) {
160
163
  return `"${value.replace(/[%\\"]/g, char => char === '%' ? '%%' : `\\${char}`)}"`;
161
164
  }
162
- export function systemdUnit(runtime, script, port, configuration, web = {}) {
165
+ export function systemdUnit(runtime, script, port, configuration, web = {}, environment = {}) {
163
166
  const config = configuration ? ` --configuration ${systemdQuote(configuration)}` : '';
164
167
  const access = `${web.bind ? ` --bind ${systemdQuote(web.bind)}` : ''}`
165
168
  + `${web.publicOrigin ? ` --public-origin ${systemdQuote(web.publicOrigin)}` : ''}`;
166
169
  return `[Unit]\nDescription=ours-fleet localhost web console\nAfter=default.target\n\n`
167
170
  + `[Service]\nType=simple\nExecStart=${systemdQuote(runtime)} ${systemdQuote(script)} web serve --port ${port} --no-open${config}${access}\n`
171
+ + Object.entries(serviceEnvironment(environment)).map(([key, value]) => `Environment=${systemdQuote(`${key}=${value}`)}\n`).join('')
168
172
  + `Restart=on-failure\nRestartSec=5\nTimeoutStopSec=15\n\n`
169
173
  + `[Install]\nWantedBy=default.target\n`;
170
174
  }
171
175
  const xml = (value) => value.replace(/[&<>"']/g, char => ({
172
176
  '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&apos;',
173
177
  }[char]));
174
- export function launchdPlist(runtime, script, port, configuration, web = {}) {
178
+ export function launchdPlist(runtime, script, port, configuration, web = {}, environment = {}) {
175
179
  const config = configuration
176
180
  ? `<string>--configuration</string><string>${xml(configuration)}</string>` : '';
177
181
  const access = `${web.bind ? `<string>--bind</string><string>${xml(web.bind)}</string>` : ''}`
@@ -181,6 +185,20 @@ export function launchdPlist(runtime, script, port, configuration, web = {}) {
181
185
  + `<plist version="1.0"><dict>\n<key>Label</key><string>${WEB_LAUNCHD_LABEL}</string>\n`
182
186
  + `<key>ProgramArguments</key><array><string>${xml(runtime)}</string><string>${xml(script)}</string><string>web</string>`
183
187
  + `<string>serve</string><string>--port</string><string>${port}</string><string>--no-open</string>${config}${access}</array>\n`
188
+ + `<key>EnvironmentVariables</key><dict>${Object.entries(serviceEnvironment(environment)).map(([key, value]) => `<key>${key}</key><string>${xml(value)}</string>`).join('')}</dict>\n`
184
189
  + `<key>RunAtLoad</key><true/><key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>\n`
185
190
  + `<key>ProcessType</key><string>Background</string>\n</dict></plist>\n`;
186
191
  }
192
+ // Retain only profile/tool paths, never inherited credentials or arbitrary environment.
193
+ function serviceEnvironment(source) {
194
+ const result = {};
195
+ for (const key of ['OURS_CONFIG', 'OURS_FLEET_HOME', 'PATH']) {
196
+ const value = source[key];
197
+ if (!value)
198
+ continue;
199
+ if (/[\r\n\0]/.test(value))
200
+ throw new FleetError('invalid_request', `invalid service path setting: ${key}`);
201
+ result[key] = key === 'PATH' ? value : resolve(value);
202
+ }
203
+ return result;
204
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/fleet",
3
- "version": "1.2.0-nightly.29",
3
+ "version": "1.2.0-nightly.30",
4
4
  "description": "Harness-agnostic fleet of persistent, identity-bound AI agents. Declarative fleet.yaml, managed native/ACP sessions, supervision, and ours.network messaging.",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-Apache-2.0",