@ours.network/install 1.2.1-nightly.3 → 1.2.1-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.
Files changed (45) hide show
  1. package/GATEWAY_SETUP.md +178 -0
  2. package/README.md +320 -9
  3. package/ROOTLESS_PODMAN_VALIDATION.md +210 -0
  4. package/assets/Dockerfile +17 -13
  5. package/assets/Dockerfile.gateway +5 -0
  6. package/assets/docker-compose.yaml +1 -1
  7. package/assets/fleet-settings.example.json +48 -0
  8. package/assets/gateway-entrypoint.sh +37 -0
  9. package/assets/release-lock.json +1122 -4471
  10. package/assets/release.json +33 -19
  11. package/assets/scripts/build/build-common.mjs +1 -1
  12. package/assets/scripts/build/build-sdk.mjs +6 -1
  13. package/assets/scripts/maintenance/build-context.mjs +1 -1
  14. package/assets/scripts/maintenance/daemon-owner.mjs +15 -0
  15. package/assets/scripts/maintenance/docker-layout-conversion.mjs +7 -3
  16. package/assets/scripts/maintenance/release-graph.mjs +18 -1
  17. package/assets/scripts/maintenance/state-operation.mjs +4 -2
  18. package/assets/scripts/runtime/client-setup.mjs +23 -5
  19. package/assets/scripts/runtime/diagnostics.mjs +72 -0
  20. package/assets/scripts/runtime/entrypoint.sh +1 -1
  21. package/assets/sources.json +46 -28
  22. package/install.mjs +7 -2
  23. package/lib/build-transition.mjs +1 -0
  24. package/lib/client-cli.mjs +127 -0
  25. package/lib/container-engine.mjs +127 -0
  26. package/lib/diagnostics.mjs +1 -0
  27. package/lib/docker-conversion-runtime.mjs +3 -2
  28. package/lib/docker-layout-installation.mjs +2 -1
  29. package/lib/docker-runtime-repair.mjs +160 -0
  30. package/lib/effects.mjs +278 -130
  31. package/lib/gateway-transition.mjs +87 -0
  32. package/lib/gateway.mjs +142 -0
  33. package/lib/legacy-migration.mjs +13 -2
  34. package/lib/orchestrate.mjs +129 -43
  35. package/lib/plan.mjs +22 -9
  36. package/lib/podman-preflight.mjs +40 -0
  37. package/lib/server-onboarding.mjs +154 -16
  38. package/lib/server-ports.mjs +17 -0
  39. package/lib/setup-options.mjs +88 -27
  40. package/lib/setup.mjs +35 -18
  41. package/lib/target.mjs +24 -8
  42. package/lib/uninstall.mjs +2 -2
  43. package/lib/usage.mjs +19 -6
  44. package/lib/workspace-setup.mjs +83 -0
  45. package/package.json +4 -2
@@ -1,43 +1,57 @@
1
1
  {
2
2
  "schema": 1,
3
3
  "channel": "nightly",
4
- "installerVersion": "1.2.1-nightly.3",
4
+ "installerVersion": "1.2.1-nightly.30",
5
5
  "packages": {
6
6
  "@ours.network/sdk": {
7
- "version": "3.8.1-nightly.3",
8
- "integrity": "sha512-iUVCs+jryoeR2Ip/uHzpkJwbpoJeLDASUaE3lC+eYDj8dbI1MBwTpeOXFemUK052O6lA4ZWrhyRgE9pJU3a+Kw=="
7
+ "version": "3.8.1-nightly.15",
8
+ "integrity": "sha512-ynKaKmKtINBqYk3kNovsgEO/3TiFY1CC9FgZzrOH+H6onnM3J//Q8cBb9sun8dl9mhB4mKZYgvneSiOejYwZUw=="
9
9
  },
10
10
  "@ours.network/cli": {
11
- "version": "2.8.1-nightly.1",
12
- "integrity": "sha512-1Rmbqlr790FhqQW/wQ//YEL7b0Q2KVSCA2TlpjaLRkj9Xh55rOoazBJjFuW/fUpTz3Z1B3MUPK9LWiYd+A0kNA=="
11
+ "version": "2.8.1-nightly.13",
12
+ "integrity": "sha512-1BeAG12vh4ruBOeJ54l60qWUhLzPLRNKnGtRiJpQmSXkamNQglrPQeXa9xW3taAM4Vg5uYgEhsz6+ieM+1TUSA=="
13
13
  },
14
14
  "@ours.network/tg-connector": {
15
- "version": "1.0.1-nightly.1",
16
- "integrity": "sha512-/Do7EfINknDks3yYndOi9aLc1alxB50qk+WKv8DRuZpDB89DDMQ+DoSGQerdvsMp32nQ3LICL8TPjcdFMZfx8Q=="
15
+ "version": "1.0.1-nightly.9",
16
+ "integrity": "sha512-wm0V8c97aIg7p26L9My36I3BE9Cy2Xc5W7WgHg6Vo+hmPvE+jXmen86yA4+DxIu9VYxoVSMtpBbu76485eCeNw=="
17
17
  },
18
18
  "@ours.network/cowork": {
19
- "version": "1.3.2-nightly.20260918.4d9242e",
20
- "integrity": "sha512-04D2lJ97SrTrkVRRbdTZ0y+KvQdG/bpnmFFjsyUqQTij+5zEkNizTG/xhcCL8m7Mb0XvVFL8WzJ62Xgh5ahlRw=="
19
+ "version": "1.3.3-nightly.20261001.6d49d3a",
20
+ "integrity": "sha512-tqoLOWc4O8CGwLdO1j6ukm6Vn57EE0zbuxKo4BWd0QVwu9ObKgHCEMOKPgW2mXHeESXSv9m7edcIrnK8I5gD+w=="
21
21
  },
22
22
  "@ours.network/messenger-server": {
23
- "version": "1.0.31-nightly.1",
24
- "integrity": "sha512-XB6N+Ppd4txmI0+2ZVFMWJxXi4DtMIDlFSHOzszNM5hopFebpw1Df+KKCK4Cgd/LZUkoOOYWGEYpib9m3RbHWw=="
23
+ "version": "1.0.31-nightly.14",
24
+ "integrity": "sha512-0lMoIadihMQqN0sdW20DF4a94Nc6bx0lnaCckSnn5VqOmvqELNdG3kRZ1Heh9VwkPEu6qNM27AR7tWpTbzvJCg=="
25
25
  },
26
26
  "@ours.network/fleet": {
27
- "version": "1.2.0-nightly.1",
28
- "integrity": "sha512-vioCobVbosmp9BgYo2XccjpPoVQd1wwV+Mt64Bm41/Vxtro2j85PUdy4AWcsY9jEFnJmj/UQfc5E5AxvlpPnrw=="
27
+ "version": "1.2.0-nightly.38",
28
+ "integrity": "sha512-glCDY/WfILgTJfG7G3ao5iEtCyWtyPOzl/o+D1EVphUuKQYZIW7Xk8sxyT2/D8FADRqvJSBbebWseWbgmlU6Ow=="
29
29
  },
30
30
  "@ours.network/mcp": {
31
- "version": "1.2.0-nightly.3",
32
- "integrity": "sha512-z0N5jn1jj9iwGRqU1d3AM2RX9mCr4T/WhASEKr+Vh52yNDMzhVjFlx0U0v8aS+GTfOQaPZTfL3TGMc17ETjh3g=="
31
+ "version": "1.2.0-nightly.10",
32
+ "integrity": "sha512-59sxJOcQagNRR+43Z5ScFlqFDBIlZSMQi8iz9mpxp5Jjl38B0dmCFsv5e+srbEYrV3xaOg9ZBsT17OUABFAseQ=="
33
33
  },
34
34
  "@ours.network/codex": {
35
- "version": "1.2.0-nightly.3",
36
- "integrity": "sha512-dIWDqbo6hJzujegHRO6x1SsBnh5p1c6gRGj/WkPha/hcN7s/27P4jqBnJ7czQyVGiNVFZ1aAxj4c9+CDLzHENw=="
35
+ "version": "1.2.0-nightly.10",
36
+ "integrity": "sha512-7hV4R0w8tiQ/JKFU5MxtM7fihd33tzWDgT+Bn8cidasxI5zmTLqDEIv6om5LM/xk7UqcPw5gDN2kcJYiybnP9Q=="
37
37
  },
38
38
  "@ours.network/claude-code": {
39
- "version": "1.2.0-nightly.3",
40
- "integrity": "sha512-7ta6KnvoHB2i/yoaykhXvzKFQTIOPtDHeTzG1PRwHjdTJ4vcGEh4jWYMd2c1jjj73lC3q9qW3LYSRutIJovtQg=="
39
+ "version": "1.2.0-nightly.10",
40
+ "integrity": "sha512-C3+H/vXespanVj9E1uQAWno9RFrI5KlK2SHSc7dnmRRuFVyRsoVcbvlpVI3nZ8snFaKVzbetebZr7vxd73AWSg=="
41
+ },
42
+ "@ours.network/daemon": {
43
+ "version": "3.8.1-nightly.7",
44
+ "integrity": "sha512-dDFDm+xERplzu4yXKvLjKGNaIdr9u4ku5/4jiI4g8do3qOX9VS0tF1+O67uSu4jqkypg860UdZRdn9Zce72H/A=="
45
+ }
46
+ },
47
+ "hostCli": {
48
+ "@ours.network/sdk": {
49
+ "version": "3.8.1-nightly.15",
50
+ "integrity": "sha512-ynKaKmKtINBqYk3kNovsgEO/3TiFY1CC9FgZzrOH+H6onnM3J//Q8cBb9sun8dl9mhB4mKZYgvneSiOejYwZUw=="
51
+ },
52
+ "@ours.network/cli": {
53
+ "version": "2.8.1-nightly.13",
54
+ "integrity": "sha512-1BeAG12vh4ruBOeJ54l60qWUhLzPLRNKnGtRiJpQmSXkamNQglrPQeXa9xW3taAM4Vg5uYgEhsz6+ieM+1TUSA=="
41
55
  }
42
56
  }
43
57
  }
@@ -7,7 +7,7 @@ export const ROOT = resolve(process.env.OURS_BUILD_ROOT || '/opt/ours');
7
7
  export const SOURCE_ROOT = resolve(process.env.OURS_SOURCE_ROOT || '/src');
8
8
  export const OUT = join(ROOT, 'docker/vendor');
9
9
  export const CONFIG = JSON.parse(readFileSync(join(ROOT, 'sources.json'), 'utf8'));
10
- export const SELECTED = new Set((process.env.OURS_BUILD_PACKAGES || 'sdk,cli,mcp,tg-connector,cowork,messenger-server').split(',').map(name => '@ours.network/' + name));
10
+ export const SELECTED = new Set((process.env.OURS_BUILD_PACKAGES || 'sdk,cli,daemon,tg-connector,cowork,messenger-server').split(',').map(name => '@ours.network/' + name));
11
11
  export const inheritedLock = process.env.OURS_INSTALLER_LOCK_FD === '3' ? [3] : [];
12
12
  export const run = (args, cwd) => execFileSync(args[0], args.slice(1), { cwd, stdio: ['inherit', 'inherit', 'inherit', ...inheritedLock] });
13
13
  export const capture = (args, cwd, env = process.env) => execFileSync(args[0], args.slice(1), { cwd, env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit', ...inheritedLock] });
@@ -2,8 +2,13 @@ import { join } from 'node:path';
2
2
  import { SOURCE_ROOT, SELECTED, pack, run } from './build-common.mjs';
3
3
  const source = join(SOURCE_ROOT, 'sdk');
4
4
  run(['npm', 'ci', '--no-audit', '--no-fund'], source);
5
- run(['bash', 'scripts/compile-mufl.sh'], source);
5
+ if (SELECTED.has('@ours.network/daemon')) run(['bash', 'scripts/compile-mufl.sh'], source);
6
6
  run(['npm', 'run', 'build'], source);
7
7
  if (SELECTED.has('@ours.network/cli')) run(['npm', 'run', 'build:cli'], source);
8
8
  pack(source, '@ours.network/sdk', 'sdk');
9
9
  pack(join(source, 'packages/cli'), '@ours.network/cli', 'sdk');
10
+
11
+ if (SELECTED.has('@ours.network/daemon')) {
12
+ run(['npm', 'run', 'build:daemon'], source);
13
+ pack(join(source, 'packages/daemon'), '@ours.network/daemon', 'sdk');
14
+ }
@@ -8,7 +8,7 @@ import { semanticRecordEqual } from './provenance-compare.mjs';
8
8
  export const BASE_RECORDS = ['package-lock.json', 'dependency-tree.json'];
9
9
  export const CONTEXT = 'build-context.json';
10
10
  const admitted = new WeakMap();
11
- const packages = new Set(['sdk', 'cli', 'mcp', 'tg-connector', 'cowork', 'messenger-server', 'fleet', 'codex', 'claude-code', 'install'].map(n => '@ours.network/' + n));
11
+ const packages = new Set(['sdk', 'cli', 'daemon', 'mcp', 'tg-connector', 'cowork', 'messenger-server', 'fleet', 'codex', 'claude-code', 'install'].map(n => '@ours.network/' + n));
12
12
  const digest = (bytes, algorithm = 'sha256', encoding = 'hex') => createHash(algorithm).update(bytes).digest(encoding);
13
13
  const fail = text => { throw new Error(`Build context: ${text}`); };
14
14
  const keys = (v, expected) => v && !Array.isArray(v) && typeof v === 'object' && Object.keys(v).sort().join('\0') === [...expected].sort().join('\0');
@@ -0,0 +1,15 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ // Native callers provide an exact selected executable. Compose mounts the
5
+ // selected runtime and supplies its bin directory, including retained releases.
6
+ export function daemonOwner(env = process.env) {
7
+ if (env.OURS_CLI_PATH) return env.OURS_CLI_PATH;
8
+ const bin = env.OURS_DAEMON_BIN_DIR;
9
+ if (!bin) throw new Error('daemon maintenance requires its selected executable or bin directory');
10
+ const daemon = join(bin, 'ours-daemon');
11
+ if (existsSync(daemon)) return daemon;
12
+ const legacy = join(bin, 'ours');
13
+ if (existsSync(legacy)) return legacy;
14
+ throw new Error('selected runtime contains no daemon administrator');
15
+ }
@@ -6,6 +6,8 @@ import { pathToFileURL } from 'node:url';
6
6
  import { copyPrivateTree, scanSource, createArchive, validateArchive } from './state-archive.mjs';
7
7
  import { publishNoReplace } from './state-native.mjs';
8
8
 
9
+ import { daemonOwner } from './daemon-owner.mjs';
10
+
9
11
  const COMPONENTS = ['daemon', 'telegram', 'cowork', 'messenger'];
10
12
  import { recordNames, readBuildRecords, validateBuildRecordSet } from './build-context.mjs';
11
13
  const DAEMON_STATE = '/var/lib/ours';
@@ -113,12 +115,14 @@ export function validateDockerLayout(tree, options) {
113
115
  const config = join(tree, component, 'config.json');
114
116
  if (fs.existsSync(config)) objectAt(config, options);
115
117
  }
118
+ // Fresh managed installs keep the MCP profile separately until configured.
119
+ // Retained embedded configuration, when present, must still match exactly.
116
120
  const daemon = objectAt(join(tree, 'daemon/config.json'), options);
117
121
  const profile = objectAt(join(tree, 'mcp/profile.json'), options);
118
122
  validateProfile(profile, options.instanceId);
119
123
  if (daemon.stateDir !== DAEMON_STATE || daemon.port !== 3050
120
- || daemon.networkMcp?.applicationConfigPath !== join(MCP_STATE, 'config.json')
121
- || JSON.stringify(daemon.networkMcp.profile) !== JSON.stringify(profile)
124
+ || (daemon.networkMcp !== undefined && (daemon.networkMcp?.applicationConfigPath !== join(MCP_STATE, 'config.json')
125
+ || JSON.stringify(daemon.networkMcp?.profile) !== JSON.stringify(profile)))
122
126
  || objectAt(join(tree, 'cowork/config.json'), options).stateDir !== '/var/lib/ours-cowork') {
123
127
  throw new Error('Converted Docker deployment configuration is inconsistent');
124
128
  }
@@ -207,7 +211,7 @@ export async function runDockerLayoutCommand(argv, env = process.env) {
207
211
  const build = env.OURS_BUILD_ROOT || '/opt/ours';
208
212
  const options = {
209
213
  uid: process.getuid(), gid: process.getgid(), instanceId: env.OURS_DAEMON_ID,
210
- cli: env.OURS_CLI_PATH || '/opt/ours/node_modules/.bin/ours',
214
+ cli: daemonOwner({ OURS_DAEMON_BIN_DIR: '/opt/ours/node_modules/.bin', ...env }),
211
215
  configPath: env.OURS_DAEMON_CONFIG || '/var/lib/ours/config.json',
212
216
  provenance: readBuildRecords(build),
213
217
  };
@@ -20,10 +20,19 @@ export function releaseBinding(policy) {
20
20
  if (release?.schema !== 1 || !['stable', 'nightly'].includes(release.channel)) fail('invalid release binding');
21
21
  const pattern = release.channel === 'nightly' ? nightly : stable;
22
22
  if (typeof release.installerVersion !== 'string' || !pattern.test(release.installerVersion)) fail('invalid installer version/channel');
23
- if (!release.packages || JSON.stringify(Object.keys(release.packages).sort()) !== JSON.stringify([...names].sort())) fail('release must select exactly nine ours packages');
23
+ if (release.scope !== undefined && release.scope !== 'host-cli') fail('invalid release scope');
24
+ const required = release.scope === 'host-cli' ? ['@ours.network/cli', '@ours.network/sdk'] : [...names, ...(release.packages?.['@ours.network/daemon'] ? ['@ours.network/daemon'] : [])];
25
+ if (!release.packages || JSON.stringify(Object.keys(release.packages).sort()) !== JSON.stringify([...required].sort())) fail('release must select exactly the required ours packages');
26
+ if (release.scope === 'host-cli' && Object.hasOwn(release, 'hostCli')) fail('nested host CLI selection');
24
27
  for (const [name, entry] of Object.entries(release.packages)) {
25
28
  if (typeof entry?.version !== 'string' || !pattern.test(entry.version) || !/^sha512-[A-Za-z0-9+/]{86}==$/.test(entry.integrity ?? '')) fail(`invalid release artifact: ${name}`);
26
29
  }
30
+ if (Object.hasOwn(release, 'hostCli')) {
31
+ if (!release.hostCli || Object.keys(release.hostCli).sort().join(',') !== '@ours.network/cli,@ours.network/sdk') fail('host CLI must select exactly CLI and SDK');
32
+ for (const [name, entry] of Object.entries(release.hostCli)) {
33
+ if (typeof entry?.version !== 'string' || !pattern.test(entry.version) || !/^sha512-[A-Za-z0-9+/]{86}==$/.test(entry.integrity ?? '')) fail(`invalid host CLI artifact: ${name}`);
34
+ }
35
+ }
27
36
  if (!policy.packages || !Object.keys(policy.packages).length) fail('missing release source selection');
28
37
  for (const [name, entry] of Object.entries(policy.packages)) {
29
38
  if (entry?.type !== 'npm' || entry.version !== release.packages[name]?.version || Object.keys(entry).sort().join(',') !== 'type,version') fail(`source selection differs from release: ${name}`);
@@ -92,3 +101,11 @@ export function verifyRuntimeRelease(root) {
92
101
  if (!existsSync(path)) return { verified: false, reason: 'legacy runtime without sources' };
93
102
  return verifyReleaseGraph(root, json(path));
94
103
  }
104
+
105
+ /** The client command has its own exact dependency selection, never a graph bypass. */
106
+ export function hostCliPolicy(policy) {
107
+ const release = releaseBinding(policy);
108
+ if (!release?.hostCli) return null;
109
+ const { hostCli, ...base } = release;
110
+ return { release: { ...base, scope: 'host-cli', packages: hostCli }, packages: Object.fromEntries(Object.entries(hostCli).map(([name, p]) => [name, { type: 'npm', version: p.version }])) };
111
+ }
@@ -9,6 +9,8 @@ import { createArchive, extractArchive, validateArchive, scanSource, copyPrivate
9
9
  import { exchange, tryLock, setMtimeNs } from './state-native.mjs';
10
10
  import { recordNames, readBuildRecords, equalBuildRecords, initializeBuildMarker } from './build-context.mjs';
11
11
 
12
+ import { daemonOwner } from './daemon-owner.mjs';
13
+
12
14
  const PROVENANCE = '.ours-provenance';
13
15
  const APPLICATIONS = ['daemon', 'telegram', 'cowork', 'messenger'];
14
16
  const exists = path => { try { fs.lstatSync(path); return true; } catch (e) { if (e.code === 'ENOENT') return false; throw e; } };
@@ -145,9 +147,9 @@ export async function runStateOperation(argv, env = process.env, checkpoints = {
145
147
  }
146
148
  function retainAuthority(staging) {
147
149
  if (!['daemon', 'server'].includes(domain)) return;
148
- if (!env.OURS_CLI_PATH || !env.OURS_DAEMON_CONFIG) fail('daemon maintenance requires its selected executable and configuration');
150
+ if (!env.OURS_DAEMON_CONFIG) fail('daemon maintenance requires its selected executable and configuration');
149
151
  const daemon = commonTree ? join(staging, 'daemon') : staging;
150
- owner([env.OURS_CLI_PATH, 'config', 'access-retain', '--config', env.OURS_DAEMON_CONFIG, '--target-state-dir', daemon, '--json']);
152
+ owner([daemonOwner(env), 'config', 'access-retain', '--config', env.OURS_DAEMON_CONFIG, '--target-state-dir', daemon, '--json']);
151
153
  if (!commonTree) return;
152
154
  const credentials = join(live, 'credentials'), destination = join(staging, 'credentials');
153
155
  for (const name of ['telegram', 'cowork', 'messenger']) if (!privateStat(join(credentials, name, 'daemon-token'), false, 0o600).size) fail('current managed credential is empty');
@@ -1,8 +1,9 @@
1
1
  import { readBuildRecords, initializeBuildMarker } from '../maintenance/build-context.mjs';
2
+ import { redactDiagnostic } from './diagnostics.mjs';
2
3
  import { spawnSync } from 'node:child_process';
3
4
  import { randomBytes } from 'node:crypto';
4
5
  import {
5
- closeSync, constants, fstatSync, lstatSync, mkdirSync, openSync, readFileSync,
6
+ existsSync, closeSync, constants, fstatSync, lstatSync, mkdirSync, openSync, readFileSync,
6
7
  realpathSync, renameSync, unlinkSync, writeFileSync, readdirSync, chmodSync, chownSync,
7
8
  } from 'node:fs';
8
9
 
@@ -12,6 +13,7 @@ const DELIVERY_FILES = [
12
13
  '/credentials/messenger/daemon-token',
13
14
  ];
14
15
  const fail = (message) => { throw new Error(message); };
16
+ let diagnosticStage = 'validation';
15
17
  const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
16
18
 
17
19
  function privatePath(path, directory, exact = directory ? 0o700 : 0o600) {
@@ -67,7 +69,7 @@ function validateCommon(idName = 'OURS_DAEMON_ID') {
67
69
 
68
70
  function composeConfig(domain) {
69
71
  if (domain === 'daemon') {
70
- const value = { stateDir: '/var/lib/ours', port: 3050, apiVisibility: 'owner', networkMcp: { profile: { endpoint: 'http://127.0.0.1:3050', expectedInstanceId: process.env.OURS_DAEMON_ID, credentialPath: '/var/lib/ours/daemon-token' }, applicationConfigPath: '/var/lib/ours-mcp/config.json' } };
72
+ const value = { stateDir: '/var/lib/ours', port: 3050, apiVisibility: 'owner' };
71
73
  if (process.env.OURS_BROKER_URL) value.brokerUrl = process.env.OURS_BROKER_URL;
72
74
  return value;
73
75
  }
@@ -128,10 +130,13 @@ function prepare() {
128
130
 
129
131
  function finishDaemonSetup() {
130
132
  const state = '/var/lib/ours';
133
+ diagnosticStage = 'daemon-provenance';
131
134
  initializeBuildMarker(`${state}/.ours-provenance`, readBuildRecords('/opt/ours'));
135
+ diagnosticStage = 'mcp-directory';
132
136
  privatePath('/var/lib/ours-mcp', true);
133
137
  const mcpProfile = { endpoint: 'http://127.0.0.1:3050', expectedInstanceId: process.env.OURS_DAEMON_ID, credentialPath: `${state}/daemon-token` };
134
138
  const mcpPath = '/var/lib/ours-mcp/profile.json';
139
+ diagnosticStage = 'mcp-profile';
135
140
  if (!existingPrivate(mcpPath, false)) atomicJson(mcpPath, mcpProfile);
136
141
  else if (JSON.stringify(jsonObject(mcpPath)) !== JSON.stringify(mcpProfile)) fail('Existing MCP profile differs from the selected daemon');
137
142
  }
@@ -155,10 +160,19 @@ async function telegramInput() {
155
160
  }
156
161
 
157
162
  function cliJson(args) {
158
- const result = spawnSync(process.execPath, ['/opt/ours/node_modules/@ours.network/cli/dist/cli.js', ...args], {
163
+ diagnosticStage = 'official-cli';
164
+ const result = spawnSync(process.execPath, [existsSync('/opt/ours/node_modules/@ours.network/daemon/dist/cli.js') ? '/opt/ours/node_modules/@ours.network/daemon/dist/cli.js' : '/opt/ours/node_modules/@ours.network/cli/dist/cli.js', ...args], {
159
165
  encoding: 'utf8', timeout: 60_000, maxBuffer: 1024 * 1024,
160
166
  });
161
- if (result.status !== 0) fail('Official OURS CLI operation failed');
167
+ if (result.error || result.status !== 0) {
168
+ // --json writes failures on stderr. Successful credential stdout is never
169
+ // forwarded, even if a later operation exits unsuccessfully.
170
+ let reason = 'CLI supplied no structured error reason';
171
+ for (const line of String(result.stderr ?? '').split('\n').reverse()) {
172
+ try { const value = JSON.parse(line); if (typeof value?.error?.message === 'string') { reason = value.error.message; break; } } catch {}
173
+ }
174
+ fail(`Official OURS CLI operation failed (${args[1]}; exit=${result.status ?? 'none'}; signal=${result.signal ?? 'none'}; system=${result.error?.code ?? 'none'}): ${reason}`);
175
+ }
162
176
  try { return JSON.parse(result.stdout); }
163
177
  catch { fail('Official OURS CLI returned malformed JSON'); }
164
178
  }
@@ -192,6 +206,10 @@ try {
192
206
  else if (['access-init', 'access-issue', 'access-replace'].includes(operation)) access(operation, process.argv[3]);
193
207
  else fail('usage: client-setup.mjs prepare | telegram-input | access-init | access-issue [OUTPUT] | access-replace');
194
208
  } catch (error) {
195
- console.error(`OURS client setup refused: ${error.message}`);
209
+ // Structured stderr retains the failing stage across sensitive Compose
210
+ // capture. The installer redacts secrets before exposing this envelope.
211
+ const message = redactDiagnostic(error.message);
212
+ console.error(JSON.stringify({ oursInstallerError: { stage: diagnosticStage, message } }));
213
+ console.error(`OURS client setup refused: ${message}`);
196
214
  process.exit(1);
197
215
  }
@@ -0,0 +1,72 @@
1
+ /** Retain actionable failure details without emitting credential output. */
2
+ export function redactDiagnostic(value, env = process.env) {
3
+ let text = String(value ?? '').replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '').replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, '').replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '');
4
+ for (const [key, secret] of Object.entries(env)) {
5
+ if (/(?:token|password|secret|api.?key|private.?key|invitation|enrollment|authorization|credential|access.?token|refresh.?token)/i.test(key) && typeof secret === 'string' && secret)
6
+ text = text.split(secret).join('[redacted]');
7
+ }
8
+ return text
9
+ .replace(/-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/g, '[redacted private key]')
10
+ .replace(/(Bearer\s+)[^\s"',;]+/gi, '$1[redacted]')
11
+ .replace(/((?:["']?(?:api[-_]?token|token|password|secret|api[-_]?key|private[-_]?key|invitation|enrollment|authorization|credential|access.?token|refresh.?token)["']?)\s*[:=]\s*)(?:"[^"\n]*"|'[^'\n]*'|[^\s,;}]+)/gi, '$1[redacted]')
12
+ .replace(/(--(?:setup-workspace|password|token|secret|api-key|private-key|invitation|enrollment)(?:=|\s+))\S+/gi, '$1[redacted]')
13
+ .replace(/(https?:\/\/)[^\s/@:]+:[^\s/@]+@/g, '$1[redacted]@')
14
+ .replace(/\b[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g, '[redacted credential]')
15
+ .replace(/[A-Za-z0-9_+/=-]{43,}/g, '[redacted opaque value]');
16
+ }
17
+
18
+ const bounded = text => text.length <= 65536 ? text : text.slice(0, 65536) + '\n[Diagnostic output exceeds 64 KiB; remainder omitted.]';
19
+ function credentialFailure(stderr) {
20
+ // Credential stdout can be a successful issuance followed by failure: never
21
+ // include it. Only a structured error envelope or shipped helper refusal is
22
+ // accepted from stderr; arbitrary lines/warnings remain private.
23
+ const lines = String(stderr ?? '').split('\n').reverse();
24
+ for (const line of lines) {
25
+ try {
26
+ const value = JSON.parse(line);
27
+ if (typeof value?.oursInstallerError?.message === 'string') {
28
+ const detail = value.oursInstallerError;
29
+ const stage = /^[a-z][a-z-]{0,40}$/.test(detail.stage ?? '') ? detail.stage : 'credential-operation';
30
+ return `Stage: ${stage}\nReason: ${detail.message}`;
31
+ }
32
+ if (typeof value?.error?.message === 'string') return `Reason: ${value.error.message}`;
33
+ } catch {}
34
+ }
35
+ const prefix = 'OURS client setup refused: ';
36
+ const legacy = lines.find(line => line.startsWith(prefix));
37
+ if (legacy) return `Reason: ${legacy.slice(prefix.length)}`;
38
+ return 'The credential operation provided no structured reason. Its raw output was withheld because it can contain credentials.';
39
+ }
40
+
41
+ export function commandFailure(command, args, result, { sensitive = false, cwd, env = process.env } = {}) {
42
+ const visibleArgs = [...args];
43
+ if (sensitive) {
44
+ for (let i = 0; i < visibleArgs.length - 1; i++) if (['-e', '--eval'].includes(visibleArgs[i])) visibleArgs[i + 1] = '[captured installer helper]';
45
+ }
46
+ const details = [`Command: ${command} ${visibleArgs.join(' ')}${result.status != null ? ` exited ${result.status}` : ''}`];
47
+ if (cwd) details.push(`Working directory: ${cwd}`);
48
+ if (result.status !== null && result.status !== undefined) details.push(`Exit code: ${result.status}`);
49
+ if (result.signal) details.push(`Signal: ${result.signal}`);
50
+ if (result.error?.code) details.push(`System error: ${result.error.code}`);
51
+ if (result.error?.code === 'ETIMEDOUT') details.push('Reason: command timed out.');
52
+ if (sensitive) details.push(credentialFailure(result.stderr));
53
+ else {
54
+ const stderr = String(result.stderr ?? '').trim(), stdout = String(result.stdout ?? '').trim();
55
+ if (stderr) details.push(`Standard error:\n${stderr}`);
56
+ if (stdout) details.push(`Standard output:\n${stdout}`);
57
+ if (!stderr && !stdout) details.push('No captured command output. Streaming commands print their output above.');
58
+ }
59
+ details.push('Keep installation data intact. Include this error and the preceding installation stage when requesting help.');
60
+ return new Error(bounded(redactDiagnostic(details.join('\n'), env)), result.error ? { cause: result.error } : undefined);
61
+ }
62
+
63
+ export function installerFailure(error, env = process.env) {
64
+ const details = []; const seen = new Set();
65
+ for (let cause = error; cause && !seen.has(cause) && seen.size < 8; cause = cause.cause) {
66
+ seen.add(cause);
67
+ const message = cause instanceof Error ? cause.message : String(cause);
68
+ if (!details.includes(message)) details.push(message);
69
+ if (typeof cause.code === 'string' && /^[A-Z_0-9]+$/.test(cause.code)) details.push(`System error: ${cause.code}`);
70
+ }
71
+ return bounded(redactDiagnostic(details.join('\nCaused by: '), env));
72
+ }
@@ -10,4 +10,4 @@ else
10
10
  exit "$status"
11
11
  fi
12
12
  node /opt/ours/docker/check-start.mjs
13
- exec node /opt/ours/node_modules/@ours.network/cli/dist/cli.js daemon serve
13
+ exec node /opt/ours/node_modules/@ours.network/daemon/dist/cli.js serve
@@ -2,82 +2,100 @@
2
2
  "release": {
3
3
  "schema": 1,
4
4
  "channel": "nightly",
5
- "installerVersion": "1.2.1-nightly.3",
5
+ "installerVersion": "1.2.1-nightly.30",
6
6
  "packages": {
7
7
  "@ours.network/sdk": {
8
- "version": "3.8.1-nightly.3",
9
- "integrity": "sha512-iUVCs+jryoeR2Ip/uHzpkJwbpoJeLDASUaE3lC+eYDj8dbI1MBwTpeOXFemUK052O6lA4ZWrhyRgE9pJU3a+Kw=="
8
+ "version": "3.8.1-nightly.15",
9
+ "integrity": "sha512-ynKaKmKtINBqYk3kNovsgEO/3TiFY1CC9FgZzrOH+H6onnM3J//Q8cBb9sun8dl9mhB4mKZYgvneSiOejYwZUw=="
10
10
  },
11
11
  "@ours.network/cli": {
12
- "version": "2.8.1-nightly.1",
13
- "integrity": "sha512-1Rmbqlr790FhqQW/wQ//YEL7b0Q2KVSCA2TlpjaLRkj9Xh55rOoazBJjFuW/fUpTz3Z1B3MUPK9LWiYd+A0kNA=="
12
+ "version": "2.8.1-nightly.13",
13
+ "integrity": "sha512-1BeAG12vh4ruBOeJ54l60qWUhLzPLRNKnGtRiJpQmSXkamNQglrPQeXa9xW3taAM4Vg5uYgEhsz6+ieM+1TUSA=="
14
14
  },
15
15
  "@ours.network/tg-connector": {
16
- "version": "1.0.1-nightly.1",
17
- "integrity": "sha512-/Do7EfINknDks3yYndOi9aLc1alxB50qk+WKv8DRuZpDB89DDMQ+DoSGQerdvsMp32nQ3LICL8TPjcdFMZfx8Q=="
16
+ "version": "1.0.1-nightly.9",
17
+ "integrity": "sha512-wm0V8c97aIg7p26L9My36I3BE9Cy2Xc5W7WgHg6Vo+hmPvE+jXmen86yA4+DxIu9VYxoVSMtpBbu76485eCeNw=="
18
18
  },
19
19
  "@ours.network/cowork": {
20
- "version": "1.3.2-nightly.20260918.4d9242e",
21
- "integrity": "sha512-04D2lJ97SrTrkVRRbdTZ0y+KvQdG/bpnmFFjsyUqQTij+5zEkNizTG/xhcCL8m7Mb0XvVFL8WzJ62Xgh5ahlRw=="
20
+ "version": "1.3.3-nightly.20261001.6d49d3a",
21
+ "integrity": "sha512-tqoLOWc4O8CGwLdO1j6ukm6Vn57EE0zbuxKo4BWd0QVwu9ObKgHCEMOKPgW2mXHeESXSv9m7edcIrnK8I5gD+w=="
22
22
  },
23
23
  "@ours.network/messenger-server": {
24
- "version": "1.0.31-nightly.1",
25
- "integrity": "sha512-XB6N+Ppd4txmI0+2ZVFMWJxXi4DtMIDlFSHOzszNM5hopFebpw1Df+KKCK4Cgd/LZUkoOOYWGEYpib9m3RbHWw=="
24
+ "version": "1.0.31-nightly.14",
25
+ "integrity": "sha512-0lMoIadihMQqN0sdW20DF4a94Nc6bx0lnaCckSnn5VqOmvqELNdG3kRZ1Heh9VwkPEu6qNM27AR7tWpTbzvJCg=="
26
26
  },
27
27
  "@ours.network/fleet": {
28
- "version": "1.2.0-nightly.1",
29
- "integrity": "sha512-vioCobVbosmp9BgYo2XccjpPoVQd1wwV+Mt64Bm41/Vxtro2j85PUdy4AWcsY9jEFnJmj/UQfc5E5AxvlpPnrw=="
28
+ "version": "1.2.0-nightly.38",
29
+ "integrity": "sha512-glCDY/WfILgTJfG7G3ao5iEtCyWtyPOzl/o+D1EVphUuKQYZIW7Xk8sxyT2/D8FADRqvJSBbebWseWbgmlU6Ow=="
30
30
  },
31
31
  "@ours.network/mcp": {
32
- "version": "1.2.0-nightly.3",
33
- "integrity": "sha512-z0N5jn1jj9iwGRqU1d3AM2RX9mCr4T/WhASEKr+Vh52yNDMzhVjFlx0U0v8aS+GTfOQaPZTfL3TGMc17ETjh3g=="
32
+ "version": "1.2.0-nightly.10",
33
+ "integrity": "sha512-59sxJOcQagNRR+43Z5ScFlqFDBIlZSMQi8iz9mpxp5Jjl38B0dmCFsv5e+srbEYrV3xaOg9ZBsT17OUABFAseQ=="
34
34
  },
35
35
  "@ours.network/codex": {
36
- "version": "1.2.0-nightly.3",
37
- "integrity": "sha512-dIWDqbo6hJzujegHRO6x1SsBnh5p1c6gRGj/WkPha/hcN7s/27P4jqBnJ7czQyVGiNVFZ1aAxj4c9+CDLzHENw=="
36
+ "version": "1.2.0-nightly.10",
37
+ "integrity": "sha512-7hV4R0w8tiQ/JKFU5MxtM7fihd33tzWDgT+Bn8cidasxI5zmTLqDEIv6om5LM/xk7UqcPw5gDN2kcJYiybnP9Q=="
38
38
  },
39
39
  "@ours.network/claude-code": {
40
- "version": "1.2.0-nightly.3",
41
- "integrity": "sha512-7ta6KnvoHB2i/yoaykhXvzKFQTIOPtDHeTzG1PRwHjdTJ4vcGEh4jWYMd2c1jjj73lC3q9qW3LYSRutIJovtQg=="
40
+ "version": "1.2.0-nightly.10",
41
+ "integrity": "sha512-C3+H/vXespanVj9E1uQAWno9RFrI5KlK2SHSc7dnmRRuFVyRsoVcbvlpVI3nZ8snFaKVzbetebZr7vxd73AWSg=="
42
+ },
43
+ "@ours.network/daemon": {
44
+ "version": "3.8.1-nightly.7",
45
+ "integrity": "sha512-dDFDm+xERplzu4yXKvLjKGNaIdr9u4ku5/4jiI4g8do3qOX9VS0tF1+O67uSu4jqkypg860UdZRdn9Zce72H/A=="
46
+ }
47
+ },
48
+ "hostCli": {
49
+ "@ours.network/sdk": {
50
+ "version": "3.8.1-nightly.15",
51
+ "integrity": "sha512-ynKaKmKtINBqYk3kNovsgEO/3TiFY1CC9FgZzrOH+H6onnM3J//Q8cBb9sun8dl9mhB4mKZYgvneSiOejYwZUw=="
52
+ },
53
+ "@ours.network/cli": {
54
+ "version": "2.8.1-nightly.13",
55
+ "integrity": "sha512-1BeAG12vh4ruBOeJ54l60qWUhLzPLRNKnGtRiJpQmSXkamNQglrPQeXa9xW3taAM4Vg5uYgEhsz6+ieM+1TUSA=="
42
56
  }
43
57
  }
44
58
  },
45
59
  "packages": {
46
60
  "@ours.network/sdk": {
47
61
  "type": "npm",
48
- "version": "3.8.1-nightly.3"
62
+ "version": "3.8.1-nightly.15"
49
63
  },
50
64
  "@ours.network/cli": {
51
65
  "type": "npm",
52
- "version": "2.8.1-nightly.1"
66
+ "version": "2.8.1-nightly.13"
53
67
  },
54
68
  "@ours.network/tg-connector": {
55
69
  "type": "npm",
56
- "version": "1.0.1-nightly.1"
70
+ "version": "1.0.1-nightly.9"
57
71
  },
58
72
  "@ours.network/cowork": {
59
73
  "type": "npm",
60
- "version": "1.3.2-nightly.20260918.4d9242e"
74
+ "version": "1.3.3-nightly.20261001.6d49d3a"
61
75
  },
62
76
  "@ours.network/messenger-server": {
63
77
  "type": "npm",
64
- "version": "1.0.31-nightly.1"
78
+ "version": "1.0.31-nightly.14"
65
79
  },
66
80
  "@ours.network/fleet": {
67
81
  "type": "npm",
68
- "version": "1.2.0-nightly.1"
82
+ "version": "1.2.0-nightly.38"
69
83
  },
70
84
  "@ours.network/mcp": {
71
85
  "type": "npm",
72
- "version": "1.2.0-nightly.3"
86
+ "version": "1.2.0-nightly.10"
73
87
  },
74
88
  "@ours.network/codex": {
75
89
  "type": "npm",
76
- "version": "1.2.0-nightly.3"
90
+ "version": "1.2.0-nightly.10"
77
91
  },
78
92
  "@ours.network/claude-code": {
79
93
  "type": "npm",
80
- "version": "1.2.0-nightly.3"
94
+ "version": "1.2.0-nightly.10"
95
+ },
96
+ "@ours.network/daemon": {
97
+ "type": "npm",
98
+ "version": "3.8.1-nightly.7"
81
99
  }
82
100
  }
83
101
  }
package/install.mjs CHANGED
@@ -3,6 +3,8 @@
3
3
  import { readFileSync } from 'node:fs';
4
4
 
5
5
  import { realEffects } from './lib/effects.mjs';
6
+ import { installerFailure } from './lib/diagnostics.mjs';
7
+ import { runWorkspaceSetup } from './lib/workspace-setup.mjs';
6
8
  import { runSetup } from './lib/setup.mjs';
7
9
  import { closeSync, makeWriter, openTty } from './lib/ui.mjs';
8
10
 
@@ -10,7 +12,10 @@ const version = JSON.parse(readFileSync(new URL('./package.json', import.meta.ur
10
12
  const ttyFd = openTty();
11
13
 
12
14
  try {
13
- const code = await runSetup(process.argv.slice(2), realEffects({
15
+ const args=process.argv.slice(2);
16
+ const workspace=args.some(v=>v==='--setup-workspace' || v.startsWith('--setup-workspace=') || v==='--setup-workspace-file' || v.startsWith('--setup-workspace-file='));
17
+ const execute=workspace ? (args,effects)=>runWorkspaceSetup(args,effects,runSetup) : runSetup;
18
+ const code = await execute(args, realEffects({
14
19
  write: makeWriter(ttyFd),
15
20
  ttyFd,
16
21
  env: process.env,
@@ -18,7 +23,7 @@ try {
18
23
  }));
19
24
  process.exitCode = code;
20
25
  } catch (error) {
21
- process.stderr.write(`ours-install: ${error instanceof Error ? error.message : String(error)}\n`);
26
+ process.stderr.write(`ours-install: ${installerFailure(error)}\n`);
22
27
  process.exitCode = 1;
23
28
  } finally {
24
29
  if (ttyFd != null) {
@@ -53,6 +53,7 @@ export async function serverBuildTransition(record, args, effects) {
53
53
  }
54
54
  await stage('Verify retained state and identities', () => effects.validateServerBuildState(record));
55
55
  await stage('Restore previously running services and check readiness', () => effects.serverLifecycle(record, 'start', runningServices));
56
+ if (record.gateway && ['daemon', 'cowork', 'gateway'].every(name => runningServices.includes(name))) await stage('Verify authenticated gateway management', () => effects.verifyGateway(record));
56
57
  const completed = { ...record, sourcePolicyHash: candidate.sourcePolicyHash };
57
58
  delete completed.buildTransition;
58
59
  effects.writeJson(path, JSON.stringify(completed, null, 2) + '\n');